Qu'est-ce que l'opérateur JavaScript >>> et comment l'utilisez-vous?

150

Je regardais du code de Mozilla qui ajoutait une méthode de filtrage à Array et il y avait une ligne de code qui me déroutait.

var len = this.length >>> 0;

Je n'ai jamais vu >>> utilisé en JavaScript auparavant.
Qu'est-ce que c'est et que fait-il?

Kenneth J
la source
@CMS Vrai, ce code / question vient de ceux-ci; cependant, les réponses ici sont plus précises et plus utiles que celles précédentes.
Justin Johnson
2
Ou c'est un bug ou les gars de Mozilla supposent que this.length pourrait être -1. >>> est un opérateur de décalage non signé, donc var len sera toujours égal ou supérieur à 0.
user347594
1
Ash Searle a trouvé une utilité pour cela - renversant l'implémentation du seigneur de JS (Doug Crockford) sur Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (bien qu'il ait fait les tests, haha).
Dan Beam

Réponses:

212

Il ne convertit pas seulement les non-nombres en nombres, il les convertit en nombres qui peuvent être exprimés sous forme d'entiers non signés 32 bits.

Bien que les chiffres de JavaScript sont flotteurs double précision (*), les opérateurs binaires ( <<, >>, &, |et ~) sont définis en termes d'opérations sur des entiers 32 bits. Faire une opération au niveau du bit convertit le nombre en un entier signé 32 bits, perdant toutes les fractions et les bits de rang supérieur à 32, avant de faire le calcul, puis de revenir en nombre.

Donc, faire une opération au niveau du bit sans effet réel, comme un décalage vers la droite de 0 bits >>0, est un moyen rapide d'arrondir un nombre et de s'assurer qu'il est dans la plage int 32 bits. De plus, l' >>>opérateur triple , après avoir effectué son opération non signée, convertit les résultats de son calcul en nombre comme un entier non signé plutôt que l'entier signé que font les autres, de sorte qu'il peut être utilisé pour convertir les négatifs en complément à deux de 32 bits. version comme un grand nombre. L'utilisation >>>0garantit que vous avez un entier compris entre 0 et 0xFFFFFFFF.

Dans ce cas, cela est utile car ECMAScript définit les index Array en termes d'entiers non signés 32 bits. Donc, si vous essayez d'implémenter array.filterd'une manière qui duplique exactement ce que dit la norme ECMAScript Fifth Edition, vous convertiriez le nombre en int 32 bits non signé comme ceci.

(En réalité , il y a peu de besoin pratique de ce que nous espérons les gens ne vont pas être mise array.lengthà 0.5, -1, 1e21ou 'LEMONS'. Mais ce sont des auteurs JavaScript dont nous parlons, vous ne savez jamais ...)

Résumé:

1>>>0            === 1
-1>>>0           === 0xFFFFFFFF          -1>>0    === -1
1.7>>>0          === 1
0x100000002>>>0  === 2
1e21>>>0         === 0xDEA00000          1e21>>0  === -0x21600000
Infinity>>>0     === 0
NaN>>>0          === 0
null>>>0         === 0
'1'>>>0          === 1
'x'>>>0          === 0
Object>>>0       === 0

(*: eh bien, ils sont définis comme se comportant comme des flottants. Cela ne me surprendrait pas si un moteur JavaScript utilisait réellement ints quand il le pouvait, pour des raisons de performances. Mais ce serait un détail d'implémentation que vous n'auriez pas à prendre avantage de.)

bobince
la source
2
+2 en profondeur description et table, -1 parce que array.length se valide et ne peut pas être arbitrairement défini sur tout ce qui n'est pas un entier ou 0 (FF renvoie cette erreur :) RangeError: invalid array length.
Justin Johnson
4
Cependant, la spécification autorise délibérément de nombreuses fonctions Array à être appelées sur des non-Array (par exemple via Array.prototype.filter.call), donc arraypeut-être pas réellement un vrai Array: il peut s'agir d'une autre classe définie par l'utilisateur. (Malheureusement, il ne peut pas être fiable de NodeList, c'est à ce moment-là que vous voulez vraiment faire cela, car c'est un objet hôte. Cela laisse le seul endroit où vous le feriez de manière réaliste en tant que argumentspseudo-Array.)
bobince
Excellente explication et bons exemples! Malheureusement, c'est un autre aspect insensé de Javascript. Je ne comprends tout simplement pas ce qui est horrible à lancer une erreur lorsque vous recevez le mauvais type. Il est possible d'autoriser la saisie dynamique sans permettre à chaque erreur accidentelle de créer un casting de type. :(
Mike Williamson
"L'utilisation de >>> 0 garantit que vous avez un entier entre 0 et 0xFFFFFFFF." À quoi ifressemblerait l' énoncé pour cela lorsque vous essayez d'identifier que le côté gauche de l'évaluation n'était pas un int? 'lemons'>>>0 === 0 && 0 >>>0 === 0évalue comme vrai? même si les citrons est évidemment un mot ..?
Zze
58

L'opérateur de décalage vers la droite non signé est utilisé dans toutes les implémentations de méthode du tableau extra de Mozilla, pour garantir que la lengthpropriété est un entier 32 bits non signé .

La lengthpropriété des objets de tableau est décrite dans la spécification comme:

Chaque objet Array a une propriété length dont la valeur est toujours un entier non négatif inférieur à 2 32 .

Cet opérateur est le moyen le plus court pour y parvenir, les méthodes de tableau en interne utilisent l' ToUint32opération, mais cette méthode n'est pas accessible et existe sur la spécification à des fins d'implémentation.

Les implémentations de Mozilla array extras essaient d'être compatibles ECMAScript 5 , regardez la description de la Array.prototype.indexOfméthode (§ 15.4.4.14):

1. Soit O le résultat de l'appel de ToObject en passant la valeur this 
   comme argument.
2. Soit lenValue le résultat de l'appel de la méthode interne [[Get]] de O avec 
   l'argument "longueur".
3. Soit len ToUint32 (lenValue) .
....

Comme vous pouvez le voir, ils veulent juste reproduire le comportement de la ToUint32méthode pour se conformer à la spécification ES5 sur une implémentation ES3, et comme je l'ai déjà dit, l' opérateur de décalage droit non signé est le moyen le plus simple.

CMS
la source
Bien que l' implémentation des extras du tableau lié puisse être correcte (ou proche de la correction), le code est toujours un mauvais exemple de code. Peut-être même un commentaire pour clarifier l'intention résoudrait cette situation.
fmark
2
Est-il possible que la longueur d'un tableau ne soit pas un entier? Je ne peux pas imaginer cela, donc ce genre de chose ToUint32me semble un peu inutile.
Marcel Korpel
7
@Marcel: Gardez à l'esprit que la plupart des Array.prototypeméthodes sont intentionnellement génériques , elles peuvent être utilisées sur des objets de type tableau , par exemple Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. L' argumentsobjet est également un bon exemple. Pour les objets tableau purs , il est impossible de changer le type de la lengthpropriété, car ils implémentent une méthode interne spéciale [[Put ]], et lorsqu'une affectation est faite à la lengthpropriété, elle est à nouveau convertie ToUint32et d'autres actions sont entreprises, comme la suppression des index ci-dessus la nouvelle longueur ...
CMS
32

C'est l' opérateur de décalage de bit droit non signé . La différence entre cela et l' opérateur de décalage de bit droit signé est que l' opérateur de décalage de bit droit non signé ( >>> ) se remplit de zéros à partir de la gauche, et l' opérateur de décalage de bit droit signé ( >> ) se remplit du bit de signe, donc en préservant le signe de la valeur numérique lorsqu'elle est décalée.

driis
la source
Ivan, cela le décalerait de 0 place; cette déclaration ne changerait rien.
Dean J
3
@Ivan, normalement, je dirais que déplacer une valeur de zéro place n'a absolument aucun sens. Mais c'est Javascript, donc il pourrait y avoir une signification derrière cela. Je ne suis pas un gourou Javascript, mais cela pourrait être un moyen de m'assurer que la valeur est en fait un entier dans le langage Javasacript sans type.
driis
2
@Ivan, voir la réponse de Justin ci-dessous. C'est en fait un moyen de s'assurer que la variable len contient un nombre.
driis
1
De plus, se >>>convertit en un entier, ce +que ne fait pas unaire .
récursif le
this.length >>> 0 convertit un entier signé en un entier non signé. Personnellement, j'ai trouvé cela utile lors du chargement d'un fichier binaire contenant des entiers non signés.
Matt Parkins du
29

Driis a suffisamment expliqué ce qu'est l'opérateur et ce qu'il fait. Voici le sens derrière cela / pourquoi il a été utilisé:

Le passage par une direction 0ne renvoie le nombre initial et jeté nullà 0. Il semble que l'exemple de code que vous regardez utilise this.length >>> 0pour s'assurer qu'il lenest numérique même s'il this.lengthn'est pas défini.

Pour de nombreuses personnes, les opérations au niveau du bit ne sont pas claires (et Douglas Crockford / jslint suggère de ne pas utiliser de telles choses). Cela ne signifie pas que c'est mal à faire, mais des méthodes plus favorables et familières existent pour rendre le code plus lisible. Une façon de faire en sorte que plus clair lenest 0est l' une des deux méthodes suivantes.

// Cast this.length to a number
var len = +this.length;

ou

// Cast this.length to a number, or use 0 if this.length is
// NaN/undefined (evaluates to false)
var len = +this.length || 0; 
Justin Johnson
la source
1
Bien que votre deuxième solution soit parfois évaluée à NaN.. Par exemple +{}... Il est probablement préférable de combiner les deux:+length||0
James
1
this.length est dans le contexte de l'objet tableau, qui ne peut être autre chose qu'un entier non négatif (au moins en FF), donc ce n'est pas une possibilité ici. Aussi, {} || 1 renvoie {} donc vous n'êtes pas mieux si this.length est un objet. L'avantage de la conversion unaire de this.length dans la première méthode est qu'il gère les cas où this.length vaut NaN. Réponse modifiée pour refléter cela.
Justin Johnson
jslint se plaindrait également de var len = + this.length comme "des avantages déroutants". Douglas, tu es si difficile!
Bayard Randel
Douglas est pointilleux. Et bien que ses arguments soient sages et généralement bien fondés, ce qu'il dit n'est ni absolu ni évangile.
Justin Johnson
15

>>>est le unsigned opérateur de décalage à droite ( voir p. 76 de la spécification JavaScript 1.5 ), par opposition au >>, la signature opérateur de décalage vers la droite.

>>>modifie les résultats du décalage des nombres négatifs car il ne conserve pas le bit de signe lors du décalage . Les conséquences de ceci peuvent être comprises par exemple, à partir d'un interprète:

$ 1 >> 0
1
$ 0 >> 0
0
$ -1 >> 0
-1
$ 1 >>> 0
1
$ 0 >>> 0
0
$ -1 >>> 0
4294967295
$(-1 >>> 0).toString(16)
"ffffffff"
$ "cabbage" >>> 0
0

Donc, ce qui est probablement destiné à être fait ici est d'obtenir la longueur, ou 0 si la longueur n'est pas définie ou n'est pas un entier, comme dans l' "cabbage"exemple ci-dessus. Je pense que dans ce cas, il est prudent de supposer que this.lengthcela ne le sera jamais < 0. Néanmoins, je dirais que cet exemple est un mauvais hack , pour deux raisons:

  1. Le comportement de <<<lors de l'utilisation de nombres négatifs, un effet secondaire probablement pas prévu (ou susceptible de se produire) dans l'exemple ci-dessus.

  2. L'intention du code n'est pas évidente , comme le vérifie l'existence de cette question.

La meilleure pratique consiste probablement à utiliser quelque chose de plus lisible à moins que les performances ne soient absolument essentielles:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
la source
Sooo ... @johncatfish est-il correct? C'est pour s'assurer que cette longueur n'est pas négative?
Anthony
4
Le cas pourrait-il -1 >>> 0se produire et si oui, est-il vraiment souhaitable de le déplacer vers 4294967295? Il semble que cela entraînerait l'exécution de la boucle plusieurs fois plus que nécessaire.
deceze
@deceze: Sans voir l'implémentation, this.lengthil est impossible de le savoir. Pour toute implémentation "saine", la longueur d'une chaîne ne doit jamais être négative, mais alors on pourrait argumenter que dans un environnement "sain", nous pouvons supposer l'existence d'une this.lengthpropriété qui renvoie toujours un nombre entier.
fmark le
vous dites >>> ne conserve pas le bit de signe .. ok .. Donc, je devrais demander, quand nous traitons des nombres négatifs .. avant toute conversion >>> ou >>, sont-ils en complément de 2s forme, ou sont-ils sous forme d'entier signé, et comment le saurions-nous? Au fait, le complément 2s, je pense, n'est peut-être pas dit avoir un bit de signe ... c'est une alternative à la notation signée, mais il est possible de déterminer le signe d'un entier
barlop
10

Deux raisons:

  1. Le résultat de >>> est une "intégrale"

  2. undefined >>> 0 = 0 (puisque JS essaiera de forcer le LFS au contexte numérique, cela fonctionnera aussi pour "foo" >>> 0, etc.)

Rappelez-vous que les nombres dans JS ont une représentation interne de double. C'est juste un moyen "rapide" de la cohérence d'entrée de base pour la longueur.

Cependant , -1 >>> 0 (oups, probablement pas la longueur souhaitée!)


la source
0

L'exemple de code Java ci-dessous explique bien:

int x = 64;

System.out.println("x >>> 3 = "  + (x >>> 3));
System.out.println("x >> 3 = "  + (x >> 3));
System.out.println(Integer.toBinaryString(x >>> 3));
System.out.println(Integer.toBinaryString(x >> 3));

La sortie est la suivante:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
nitinsridar
la source