2009-11-30 15 views
105

Estaba viendo 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.¿Qué es el operador JavaScript >>> y cómo lo usa?

var len = this.length >>> 0; 

Nunca he visto >>> utilizado en JavaScript anteriormente.
¿Qué es y qué hace?

+0

@CMS Cierto, este código/pregunta proviene de aquellos; sin embargo, la respuesta aquí es más específica y valiosa que las anteriores. –

+2

O es un error o los chicos de Mozilla están asumiendo esto. La duración podría ser -1. >>> es un operador de desplazamiento sin signo, por lo que siempre será 0 o mayor. – user347594

+4

-1 >>> 0 === 4294967295 – jimr

Respuesta

163

No sólo convertir a los no Números de Número, que los convierte en los números que se pueden expresar como enteros de 32 bits sin signo.

Aunque los números de JavaScript son flotantes de doble precisión (*), los operadores de bits (<<, >>, &, | y ~) están definidos en términos de operaciones de enteros de 32 bits. Al hacer una operación en modo bit, el número se convierte en una int de 32 bits, perdiendo cualquier fracción y un lugar más alto que 32, antes de hacer el cálculo y luego volver a convertirlo en Number.

Por lo tanto, hacer una operación en modo bit sin efecto real, como un desplazamiento hacia la derecha de 0 bits >>0, es una manera rápida de redondear un número y asegurar que esté dentro del rango de 32 bits. Además, la triple >>> operador, después de hacer su operación sin firmar, convierte los resultados de su cálculo de número como un entero sin signo en lugar del entero con signo de los demás, por lo que puede ser usado para convertir los negativos a la de 32 bits two's- versión del complemento como un número grande. Usar >>>0 asegura que tienes un número entero entre 0 y 0xFFFFFFFF.

En este caso, esto es útil porque ECMAScript define índices de matriz en términos de enteros sin signo de 32 bits. Por lo tanto, si intenta implementar array.filter de forma que duplique exactamente lo que dice el estándar ECMAScript Fifth Edition, debería convertir el número a int sin firmar de 32 bits 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.length a 0.5, -1, 1e21 o '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, son definidos como comportarse como flotadores no me sorprendería si algún motor de JavaScript enteros utiliza realmente cuando se podía, por razones de rendimiento.. Pero eso sería un detalle de implementación y ou no llegaría a tomar ventaja de.)

+1

Descripción y tabla de +2 en profundidad, -1 porque array.length se valida a sí mismo y no se puede establecer arbitrariamente a nada que no sea un entero o 0 (FF arroja este error: 'RangeError: invalid array length'). –

+3

Sin embargo, la especificación permite deliberadamente que se invoquen muchas funciones de Array en no-Array (por ejemplo, a través de 'Array.prototype.filter.call'), por lo que' array' podría no ser una 'Array' real: podría ser un poco otra clase definida por el usuario. (Desafortunadamente, no puede ser confiablemente una NodeList, que es cuando realmente quieres hacer eso, ya que es un objeto host. Eso deja el único lugar donde realísticamente harías eso como pseudo-Array 'arguments'.) – bobince

+7

+1 para 'array.length = LIMONES'. Tengo que encontrar una persona realmente molesta para que pueda jugar este truco en su código: 3 – yoshi

28

Ese es el operador unsigned right bit shift. La diferencia entre este y el signed right bit shift operator, es que el operador sin firmar cambio poco a la derecha (>>>) se llena de ceros de la izquierda y la derecha firmó operador de desplazamiento de bits (>>) se llena con el bit de signo, preservando así el signo del valor numérico cuando se desplaza.

+2

¿Tiene sentido el código 'this.length >>> 0;'? –

+0

Ivan, eso lo cambiaría por 0 lugares; esa declaración no cambiaría nada. –

+3

@Ivan, normalmente, diría que cambiar un valor por cero lugares no tiene ningún sentido. Pero esto es Javascript, entonces puede haber un significado detrás de esto. No soy un gurú de Javascript, pero podría ser una forma de garantizar que el valor sea, en realidad, un número entero en el lenguaje de Javasacript sin tipo. – driis

26

Driis ha explicado suficientemente en qué consiste el operador es y lo que hace. Aquí está el significado detrás de él/por qué se utilizó:

Shifting cualquier dirección por 0 no devuelve el número original y echará a null0. Parece que el código de ejemplo que usted está buscando en this.length >>> 0 está utilizando para asegurar que len son numéricas incluso si this.length no está definido.

Para muchas personas, las operaciones bit a bit no son claras (y Douglas Crockford/JSLint sugiere contra el uso de este tipo de 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 de asegurarse de que len es 0 es 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; 
+0

+1 para una buena respuesta clara :) – Mottie

+1

Aunque, su segunda solución a veces evaluará a 'NaN' .. E.g. '+ {}' ... Probablemente sea mejor combinar los dos: '+ length || 0' – James

+1

this.length está en el contexto del objeto array, que no puede ser otra cosa que un entero no negativo (al menos en FF), entonces no es una posibilidad aquí. Además, {} || 1 devuelve {} por lo que no está mejor si this.length es un objeto. El beneficio de la emisión unariada de this.length en el primer método es que maneja casos donde this.length es NaN. Respuesta editada para reflejar eso. –

14

>>> es el sin firmar operador de desplazamiento a la derecha (see p. 76 of the JavaScript 1.5 specification), en contraposición a la >>, la firmaron operador de desplazamiento a la derecha.

>>> cambia los resultados del desplazamiento de los números negativos porque no conserva el bit de signo al cambiar. Las consecuencias de esto se pueden comprenderse por ejemplo, de un interpretter:

$ 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, ¿qué es, probablemente, la intención de hacer aquí es conseguir la longitud, o 0 si la duración es indefinida o no un entero, como se según el ejemplo anterior "cabbage". Creo que en este caso es seguro asumir que this.length nunca será < 0. Sin embargo, yo diría que este ejemplo es un corte desagradable, por dos razones:

  1. El comportamiento de <<< al utilizar números negativos, un efecto secundario probablemente no pretende (o que puedan producirse) en el ejemplo de arriba

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

La mejor práctica es probable que usar algo más legible menos que el cumplimiento es absolutamente crítico:

isNaN(parseInt(foo)) ? 0 : parseInt(foo) 
+0

Sooo ... @johncatfish es correcto? Es para asegurar que this.length no sea negativo? – Anthony

+4

Podría el caso de '-1 >>> 0' suceda alguna vez y si es así, ¿es realmente deseable cambiarlo a 4294967295? Parece que esto haría que el ciclo se ejecute unas cuantas veces más de lo necesario. – deceze

+0

@deceze: Sin ver la implementación de 'this.length' es imposible de saber. Para cualquier implementación" sensata "la longitud de una cadena nunca debería ser negativa, pero entonces se podría argumentar que en un ambiente" sano "podemos asumir la existencia de un' this.length 'propiedad que siempre devuelve un número entero. – fmark

9

Dos razones:

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

  2. undefined >>> 0 = 0 (Desde JS a tratar de coaccionar a la EPA a contexto numérico, esto funcionará para "foo" >>> 0, etc., así)

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

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

53

El operador sin signo de desplazamiento a la derecha se utiliza en todas las implementaciones método, el array de extra de Mozilla, para garantizar que la propiedad length es un entero sin signo de 32 bits.

La propiedad length de objetos de matriz es described en la especificación como:

Every Array object has a length property whose value is always a nonnegative integer less than 232.

Este operador es el camino más corto para lograrlo, internamente métodos de matriz utilizar la operación ToUint32, pero ese método no es accesible y existen en la especificación para fines de implementación.

Los Mozilla extras matriz implementaciones tratan de ser compatible ECMAScript 5, miran a la descripción del método de Array.prototype.indexOf (§ 15.4.4.14):

 
1. Let O be the result of calling ToObject passing the this value 
    as the argument. 
2. Let lenValue be the result of calling the [[Get]] internal method of O with 
    the argument "length". 
3. Let len be ToUint32(lenValue). 
.... 

Como se puede ver, lo que quieren reproducir el el comportamiento del método ToUint32 para cumplir con la especificación ES5 en una implementación de ES3, y como dije antes, el unsigned right shift operator es la manera más fácil.

+0

Mientras que la implementación link * array extras * puede ser correcta (o cerca de corregir) el código sigue siendo un mal ejemplo de código. Tal vez incluso un comentario para aclarar la intención resolvería esta situación. – fmark

+2

¿Es posible que la longitud de una matriz sea * no * un número entero? No me lo puedo imaginar, así que este tipo de 'ToUint32' me parece un poco innecesario. –

+6

@Marcel: tenga en cuenta que la mayoría de los métodos 'Array.prototype' son * intencionalmente genéricos *, se pueden usar en objetos * tipo-array *, p. 'Array.prototype.indexOf.call ({0: 'foo', 1: 'barra', longitud: 2}, 'barra') == 1;'. El objeto 'arguments' es también un buen ejemplo. Para los objetos * pure * array, es imposible cambiar el tipo de la propiedad 'length', porque implementan un método interno especial [[[Put'] (http://bit.ly/d5hfSL)]], y cuando se realiza una asignación a la propiedad 'length', nuevamente se convierte' ToUint32' y se toman otras acciones, como eliminar índices sobre la nueva longitud ... – CMS