¿Qué es el operador JavaScript >>> y cómo lo usa?

150

Estaba mirando el código de Mozilla que agregaba un método de filtro a Array y tenía una línea de código que me confundía.

var len = this.length >>> 0;

Nunca he visto >>> usado en JavaScript antes.
¿Qué es y para qué sirve?

Kenneth J
fuente
@ CMS Verdadero, este código / pregunta proviene de aquellos; sin embargo, la respuesta aquí es más específica y valiosa que las anteriores.
Justin Johnson el
2
O es un error o los muchachos de Mozilla están asumiendo esto. La longitud podría ser -1. >>> es un operador de desplazamiento sin signo, por lo que var len siempre será 0 o mayor.
user347594
1
Ash Searle encontró un uso para ello: revocó la implementación del señor de JS (Doug Crockford) a Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (aunque hizo las pruebas, jaja).
Dan Beam

Respuestas:

212

No solo convierte números que no son números en números, sino que los convierte en números que se pueden expresar como entradas de 32 bits sin signo.

Aunque los números de JavaScript son flotantes de doble precisión (*), los operadores de bits ( <<, >>, &, |y~ ) se definen en términos de operaciones de enteros de 32 bits. Hacer una operación a nivel de bit convierte el número a un int con signo de 32 bits, perdiendo cualquier fracción y bits de lugar superior a 32, antes de hacer el cálculo y luego volver a convertirlo en Número.

Entonces, hacer una operación bit a bit sin efecto real, como un desplazamiento hacia la derecha de 0 bits >>0, es una forma rápida de redondear un número y asegurarse de que esté en el rango int de 32 bits. Además, el >>>operador triple , después de realizar su operación sin signo, convierte los resultados de su cálculo en Número como un entero sin signo en lugar del entero con signo que hacen los demás, por lo que puede usarse para convertir negativos al complemento de dos bits de 32 bits versión como un gran número. Utilizando>>>0 garantiza que tenga un número entero entre 0 y 0xFFFFFFFF.

En este caso, esto es útil porque ECMAScript define los índices de matriz en términos de entradas de 32 bits sin signo. Entonces, si está tratando de implementar array.filterde una manera que duplica exactamente lo que dice el estándar ECMAScript Fifth Edition, debería enviar el número a int de 32 bits sin signo de esta manera.

(En realidad hay poca necesidad práctica de esto como es de esperar la gente no va a ser la creación array.lengthde 0.5, -1, 1e21o 'LEMONS'. Pero esto es autores de JavaScript que estamos hablando, así que nunca se sabe ...)

Resumen:

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

(*: bueno, se definen como comportarse como flotadores. No me sorprendería si algún motor de JavaScript realmente usara ints cuando pudiera, por razones de rendimiento. Pero eso sería un detalle de implementación que no podría tomar. Ventaja de.)

bobince
fuente
2
Descripción y tabla en profundidad de +2, -1 porque array.length se valida a sí mismo y no se puede establecer arbitrariamente en nada que no sea un entero o 0 (FF arroja este error:) RangeError: invalid array length.
Justin Johnson el
44
Sin embargo, la especificación permite deliberadamente que se invoquen muchas funciones de Array en no Array (por ejemplo, vía Array.prototype.filter.call), por lo que arraypodría no ser real Array: podría ser alguna otra clase definida por el usuario. (Por desgracia, no puede ser fiable un NodeList, que es cuando usted realmente quiere hacer eso, ya que es un objeto host que las hojas el único lugar en que realmente haría que como el. argumentsSeudo-Array.)
bobince
¡Gran explicación y excelentes ejemplos! Lamentablemente, este es otro aspecto loco de Javascript. Simplemente no entiendo qué es tan horrible acerca de lanzar un error cuando recibes el tipo incorrecto. Es posible permitir la escritura dinámica sin permitir cada error accidental para crear una conversión de tipo. :(
Mike Williamson
"Usar >>> 0 garantiza que tenga un número entero entre 0 y 0xFFFFFFFF". ¿Cómo se vería la ifdeclaración para esto al tratar de identificar que el lado izquierdo de la evaluación no era un int? 'lemons'>>>0 === 0 && 0 >>>0 === 0evalúa como verdadero? a pesar de que los limones es obviamente una palabra ..?
Zze
58

El operador de desplazamiento a la derecha sin signo se utiliza en todas las implementaciones de métodos de matriz extra de Mozilla, para garantizar que la lengthpropiedad sea un entero de 32 bits sin signo .

La lengthpropiedad de los objetos de matriz se describe en la especificación como:

Cada objeto de matriz tiene una propiedad de longitud cuyo valor es siempre un entero no negativo menor que 2 32 .

Este operador es la forma más corta de lograrlo, internamente los métodos de matriz usan la ToUint32operación, pero ese método no es accesible y existe en la especificación para propósitos de implementación.

Las implementaciones adicionales de la matriz de Mozilla intentan cumplir con ECMAScript 5 , mire la descripción del Array.prototype.indexOfmétodo (§ 15.4.4.14):

1. Sea O el resultado de llamar a ToObject pasando este valor 
   como el argumento
2. Deje que lenValue sea el resultado de llamar al método interno [[Get]] de O con 
   El argumento "longitud".
3. Deja que sea  ToUint32 (lenValue) .
....

Como puede ver, solo quieren reproducir el comportamiento del ToUint32método para cumplir con la especificación ES5 en una implementación de ES3, y como dije antes, el operador de desplazamiento a la derecha sin firmar es la forma más fácil.

CMS
fuente
Si bien la implementación de extras de la matriz vinculada puede ser correcta (o casi correcta), el código sigue siendo un mal ejemplo de código. Quizás incluso un comentario para aclarar la intención resolvería esta situación.
fmark
2
¿Es posible que la longitud de una matriz no sea un número entero? No me lo puedo imaginar, así que esto me ToUint32parece un poco innecesario.
Marcel Korpel
77
@Marcel: Tenga en cuenta que la mayoría de los Array.prototypemétodos son intencionalmente genéricos , se pueden usar en objetos similares a una matriz, por ejemplo Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. El argumentsobjeto también es un buen ejemplo. Para los objetos de matriz pura , es imposible cambiar el tipo de lengthpropiedad, ya que implementan un método interno especial [[Put ]], y cuando se realiza una asignación a la lengthpropiedad, nuevamente se convierte ToUint32y se toman otras acciones, como eliminar los índices anteriores la nueva duración ...
CMS
32

Ese es el operador de desplazamiento de bit derecho sin signo . La diferencia entre este y el operador de desplazamiento a la derecha firmado poco , es que el no firmada operador de desplazamiento a la derecha bits ( >>> ) se llena de ceros de la izquierda, y el firmado operador de desplazamiento de bits a la derecha ( >> ) se llena con el bit de signo, por lo tanto preservando el signo del valor numérico cuando se desplaza.

driis
fuente
Ivan, eso lo cambiaría por 0 lugares; esa declaración no cambiaría nada.
Dean J
3
@Ivan, normalmente, diría que cambiar un valor por cero lugares no tiene ningún sentido. Pero esto es Javascript, por lo que puede haber un significado detrás de él. No soy un gurú de Javascript, pero podría ser una forma de garantizar que el valor sea de hecho un número entero en el lenguaje Javasacript sin tipo.
driis el
2
@ Ivan, mira la respuesta de Justin a continuación. De hecho, es una forma de garantizar que la variable len contenga un número.
driis el
1
Además, se >>>convierte en un número entero, que unario +no hace.
recursivo
this.length >>> 0 convierte un entero con signo en uno sin signo. Personalmente, he encontrado esto útil al cargar un archivo binario con entradas sin firmar.
Matt Parkins
29

Driis ha explicado suficientemente qué es el operador y qué hace. Aquí está el significado detrás de esto / por qué se usó:

Cambiar cualquier dirección por 0sí devuelve el número original y se lanzará nulla 0. Parece que el código de ejemplo que está viendo está usando this.length >>> 0para asegurarse de que lensea ​​numérico, incluso sithis.length no está definido.

Para muchas personas, las operaciones bit a bit no están claras (y Douglas Crockford / jslint sugiere no usar tales cosas). No significa que sea incorrecto hacerlo, pero existen métodos más favorables y familiares para hacer que el código sea más legible. Una forma más clara para asegurarse de que lenes 0es cualquiera de los dos métodos siguientes.

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

o

// 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
fuente
1
Aunque, su segunda solución a veces se evaluaría para NaN... Por ejemplo +{}... Probablemente sea mejor combinar los dos:+length||0
James
1
this.length está en el contexto del objeto de matriz, que no puede ser más que un entero no negativo (al menos en FF), por lo que no es una posibilidad aquí. Además, {} || 1 devuelve {}, por lo que no está mejor si this.length es un objeto. El beneficio de lanzar también unary this.length en el primer método es que maneja casos donde this.length es NaN. Respuesta editada para reflejar eso.
Justin Johnson el
jslint también se quejaría de var len = + this.length como "ventajas confusas". Douglas, eres muy quisquilloso!
Bayard Randel
Douglas es exigente. Y aunque sus argumentos son sabios y típicamente bien fundados, lo que él dice no es absoluto ni evangélico.
Justin Johnson el
15

>>>es la unsigned operador de desplazamiento a la derecha ( ver p. 76 de la especificación JavaScript 1.5 ), en oposición a la >>, el firmado operador de desplazamiento a la derecha.

>>>cambia los resultados del desplazamiento de números negativos porque no conserva el bit de signo al desplazarse . Las consecuencias de esto se pueden entender por ejemplo, de un intérprete:

$ 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

Entonces, lo que probablemente se pretende hacer aquí es obtener la longitud, o 0 si la longitud no está definida o no es un número entero, según el "cabbage"ejemplo anterior. Creo que en este caso es seguro asumir que this.lengthnunca lo será < 0. Sin embargo, diría que este ejemplo es un truco desagradable , por dos razones:

  1. El comportamiento de <<<cuando se usan números negativos, un efecto secundario probablemente no intencionado (o probable que ocurra) en el ejemplo anterior.

  2. La intención del código no es obvia , como lo verifica la existencia de esta pregunta.

La mejor práctica es probablemente usar algo más legible a menos que el rendimiento sea absolutamente crítico:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
fuente
Sooo ... @johncatfish es correcto? Es para asegurar esto. ¿La longitud no es negativa?
Anthony
44
¿Podría suceder el caso -1 >>> 0y, de ser así, es realmente deseable cambiarlo a 4294967295? Parece que esto haría que el ciclo se ejecute varias veces más de lo necesario.
deceze
@deceze: Sin ver la implementación this.length, es imposible saberlo. Para cualquier implementación "sana", la longitud de una cadena nunca debe ser negativa, pero entonces uno podría argumentar que en un ambiente "sano" podemos asumir la existencia de una this.lengthpropiedad que siempre devuelve un número entero.
fmark
dices >>> no conserva el bit de signo ... ok ... Entonces, tendría que preguntar, cuando tratemos con números negativos ... antes de cualquier conversión >>> o >>, ¿están en el compás 2s? forma, o están en forma de entero con signo, y cómo lo sabríamos? Por cierto, creo que no se dice que el complemento 2s tenga un bit de signo ... es una alternativa a la notación con signo, pero es posible determinar el signo de un número entero
barlop
10

Dos razones:

  1. El resultado de >>> es una "integral"

  2. indefinido >>> 0 = 0 (dado que JS intentará forzar el LFS al contexto numérico, esto también funcionará para "foo" >>> 0, etc.)

Recuerde que los números en JS tienen una representación interna de doble. Es solo una forma "rápida" de cordura de entrada básica para la longitud.

Sin embargo , -1 >>> 0 (¡Uy, probablemente no sea la longitud deseada!)


fuente
0

El código Java de muestra a continuación explica 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 salida es la siguiente:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
nitinsridar
fuente