O que é o operador JavaScript >>> e como você o usa?

150

Eu estava olhando o código da Mozilla que adiciona um método de filtro ao Array e ele tinha uma linha de código que me confundia.

var len = this.length >>> 0;

Eu nunca vi >>> usado em JavaScript antes.
O que é isso e o que isso faz?

Kenneth J
fonte
@CMS True, esse código / pergunta vem daqueles; no entanto, a resposta aqui é mais específica e valiosa do que as anteriores.
Justin Johnson
2
Ou é um bug ou os caras da Mozilla estão assumindo que this.length pode ser -1. >>> é um operador de deslocamento não assinado, portanto var len sempre será 0 ou maior.
usar o seguinte comando
1
Ash Searle encontrou um uso para isso - derrubando o senhor da implementação de JS (Doug Crockford) para Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (embora ele tenha feito os testes, haha).
Dan feixe

Respostas:

211

Ele não converte apenas não-números em número, mas também em números que podem ser expressos como ints não assinados de 32 bits.

Embora os números de JavaScript são bóias de precisão dupla (*), os operadores bit a bit ( <<, >>, &, |e ~) são definidos em termos de operações sobre inteiros de 32 bits. Fazer uma operação bit a bit converte o número em um int assinado de 32 bits, perdendo frações e bits de posição superior a 32, antes de fazer o cálculo e depois converter novamente em Number.

Portanto, fazer uma operação bit a bit sem efeito real, como um deslocamento para a direita de 0 bits >>0, é uma maneira rápida de arredondar um número e garantir que ele esteja no intervalo int de 32 bits. Além disso, o >>>operador triplo , depois de fazer sua operação não assinada, converte os resultados de seu cálculo em Number como um número inteiro não assinado, em vez do número inteiro assinado que os outros, para que possa ser usado para converter negativos no complemento de 32 bits e dois versão como um número grande. Usar >>>0garante que você tenha um número inteiro entre 0 e 0xFFFFFFFF.

Nesse caso, isso é útil porque o ECMAScript define índices de matriz em termos de entradas não assinadas de 32 bits. Portanto, se você estiver tentando implementar array.filterde uma maneira que duplique exatamente o que o padrão ECMAScript Fifth Edition diz, você converterá o número em int não assinado de 32 bits dessa maneira.

(Na realidade, há pouca necessidade prática para isso, espero que as pessoas não vão estar definindo array.lengtha 0.5, -1, 1e21ou 'LEMONS'. Mas isso é JavaScript autores que estamos falando, então nunca se sabe ...)

Resumo:

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

(*: bem, eles são definidos como se comportando como carros alegóricos. Não me surpreenderia se algum mecanismo JavaScript realmente usasse ints quando pudesse, por razões de desempenho. Mas isso seria um detalhe de implementação que você não faria nada vantagem de.)

bobince
fonte
2
+2 na descrição e na tabela detalhadas, -1, porque o array.length se valida e não pode ser arbitrariamente definido como algo que não seja um número inteiro ou 0 (FF gera esse erro:) RangeError: invalid array length.
Justin Johnson
4
No entanto, a especificação permite deliberadamente que muitas funções Array sejam chamadas em não-Array (por exemplo, via Array.prototype.filter.call), portanto, arraypode não ser realmente real Array: pode ser alguma outra classe definida pelo usuário. (Infelizmente, não pode ser um NodeList confiável, que é quando você realmente deseja fazer isso, pois é um objeto host. Isso deixa o único lugar em que você realmente faz isso como argumentspseudo-matriz.)
bobince
Ótima explicação e ótimos exemplos! Infelizmente, esse é outro aspecto insano do Javascript. Só não entendo o que há de tão horrível em gerar um erro quando você recebe o tipo errado. É possível permitir a digitação dinâmica sem permitir que qualquer erro acidental crie uma conversão de tipo. :(
Mike Williamson
"Usar >>> 0 garante que você tenha um número inteiro entre 0 e 0xFFFFFFFF." como seria a ifafirmação disso ao tentar identificar que o lado esquerdo da avaliação não era um int? 'lemons'>>>0 === 0 && 0 >>>0 === 0avalia como verdadeiro? mesmo que limões seja obviamente uma palavra ..?
Zze 27/03
58

O operador de deslocamento à direita não assinado é usado em todas as implementações de métodos do Mozilla para todo o array extra , para garantir que a lengthpropriedade seja um número inteiro de 32 bits não assinado .

A lengthpropriedade dos objetos de matriz é descrita na especificação como:

Todo objeto Array possui uma propriedade length cujo valor é sempre um número inteiro não negativo menor que 2 32 .

Esse operador é a maneira mais curta de alcançá-lo; os métodos de matriz interna usam a ToUint32operação, mas esse método não está acessível e existe na especificação para fins de implementação.

As implementações extras do array Mozilla tentam ser compatíveis com ECMAScript 5 , veja a descrição do Array.prototype.indexOfmétodo (§ 15.4.4.14):

1. Seja O o resultado de chamar ToObject passando o valor this 
   como o argumento.
2. Seja lenValue o resultado da chamada do método interno [[Get]] de O com 
   o argumento "comprimento".
3. Seja len  ToUint32 (lenValue) .
....

Como você pode ver, eles apenas querem reproduzir o comportamento do ToUint32método para estar em conformidade com as especificações do ES5 em uma implementação do ES3 e, como eu disse antes, o operador de mudança à direita não assinado é a maneira mais fácil.

CMS
fonte
Embora a implementação de extras da matriz vinculada possa estar correta (ou quase correta), o código ainda é um exemplo de código incorreto. Talvez até um comentário para esclarecer a intenção resolva essa situação.
fmark
2
É possível que o comprimento de uma matriz não seja um número inteiro? Não consigo imaginar isso, então esse tipo de coisa me ToUint32parece um pouco desnecessário.
Marcel Korpel
7
@ Marcel: Lembre-se de que a maioria dos Array.prototypemétodos é intencionalmente genérica , eles podem ser usados ​​em objetos de matriz, por exemplo Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. O argumentsobjeto também é um bom exemplo. Para objetos puros de array, é impossível alterar o tipo da lengthpropriedade, porque eles implementam um método interno [[Put ]] especial e, quando uma atribuição é feita à lengthpropriedade, ela é convertida novamente ToUint32e outras ações são executadas, como a exclusão de índices acima. o novo comprimento ...
CMS
32

Esse é o operador de mudança de bit certo não assinado . A diferença entre este e o operador de mudança de bit direito assinado é que o operador de mudança de bit direito não assinado ( >>> ) preenche os zeros da esquerda e o operador de mudança de bit direito assinado ( >> ) preenche o bit de sinal, portanto preservando o sinal do valor numérico quando deslocado.

driis
fonte
Ivan, isso mudaria em 0 lugares; essa afirmação não mudaria nada.
Dean J
3
@ Ivan, normalmente, eu diria que mudar um valor por zero lugar não faz absolutamente nenhum sentido. Mas isso é Javascript, então pode haver um significado por trás disso. Eu não sou um guru do Javascript, mas pode ser uma maneira de garantir que o valor seja de fato um número inteiro na linguagem Javasacript sem tipo.
driis 30/11/2009
2
@ Ivan, veja a resposta de Justin abaixo. Na verdade, é uma maneira de garantir que a variável len contenha um número.
driis 30/11/2009
1
Além disso, >>>converte em um número inteiro, o que é unário +.
recursivo
this.length >>> 0 converte um número inteiro assinado em um inteiro não assinado. Pessoalmente, achei isso útil ao carregar um arquivo binário com ints não assinados.
22616 Matt Parkins
29

Driis explicou suficientemente o que é o operador e o que ele faz. Aqui está o significado por trás disso / por que foi usado:

Mudar qualquer direção por do 0retorna o número original e será convertido nullem 0. Parece que o código de exemplo que você está visualizando está usando this.length >>> 0para garantir que lenseja numérico, mesmo quethis.length não esteja definido.

Para muitas pessoas, as operações bit a bit não são claras (e Douglas Crockford / jslint sugere não usar essas coisas). Isso não significa que é errado, mas existem métodos mais favoráveis ​​e familiares para tornar o código mais legível. Uma maneira mais clara para garantir que lené 0é qualquer um dos dois métodos seguintes.

// 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
fonte
1
Embora, a sua segunda solução, às vezes, avaliar a NaN.. Ex +{}... É provavelmente o melhor para combinar os dois:+length||0
James
1
this.length está no contexto do objeto array, que não pode ser outra coisa além de um número inteiro não negativo (pelo menos em FF), portanto, não é uma possibilidade aqui. Além disso, {} || 1 retorna {} para que você não fique melhor se this.length for um objeto. O benefício de também converter esse comprimento unário no primeiro método é que ele lida com casos em que esse comprimento é NaN. Resposta editada para refletir isso.
Justin Johnson
O jslint também se queixaria de var len = + this.length como "pontos positivos confusos". Douglas, você é tão exigente!
Bayard Randel
Douglas é exigente. E embora seus argumentos sejam sábios e tipicamente bem fundamentados, o que ele diz não é absoluto nem evangelho.
Justin Johnson
15

>>>é o unsigned operador de deslocamento para a direita ( ver p. 76 da especificação JavaScript 1,5 ), ao contrário do >>, o assinado do operador deslocamento para a direita.

>>>altera os resultados da mudança de números negativos porque não preserva o bit de sinal ao mudar . As conseqüências disso podem ser entendidas por exemplo, a partir de um interpretador:

$ 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

Portanto, o que provavelmente deve ser feito aqui é obter o comprimento, ou 0, se o comprimento for indefinido ou não for um número inteiro, conforme o "cabbage"exemplo acima. Eu acho que, neste caso, é seguro assumir que this.lengthnunca será < 0. No entanto, eu argumentaria que este exemplo é um truque desagradável , por duas razões:

  1. O comportamento de <<<usar números negativos, um efeito colateral provavelmente não intencional (ou provável de ocorrer) no exemplo acima.

  2. A intenção do código não é óbvia , como verifica a existência dessa pergunta.

A melhor prática é provavelmente usar algo mais legível, a menos que o desempenho seja absolutamente crítico:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
fonte
Sooo ... @johncatfish está correto? É para garantir que esse comprimento não seja negativo?
Anthony
4
O caso -1 >>> 0poderia acontecer e, nesse caso, é realmente desejável alterá-lo para 4294967295? Parece que isso faria com que o loop fosse executado mais algumas vezes do que o necessário.
deceze
@ deceze: Sem ver a implementação this.length, é impossível saber. Para qualquer implementação "sã", o comprimento de uma string nunca deve ser negativo, mas pode-se argumentar que em um ambiente "sã" podemos assumir a existência de uma this.lengthpropriedade que sempre retorna um número inteiro.
fmark
você diz que >>> não preserva o bit do sinal .. ok .. Então, eu teria que perguntar, quando lidamos com números negativos .. antes de qualquer conversão >>> ou >>, eles estão em conformidade com 2s ou estão em forma de número inteiro assinado, e como saberíamos? A propósito, o complemento 2s acho que talvez não tenha um bit de sinal .. é uma alternativa à notação assinada, mas é possível determinar o sinal de um número inteiro
barlop
10

Duas razões:

  1. O resultado de >>> é uma "integral"

  2. undefined >>> 0 = 0 (como o JS tentará coagir o LFS ao contexto numérico, isso funcionará também para "foo" >>> 0, etc.)

Lembre-se de que os números em JS têm uma representação interna de double. É apenas uma maneira "rápida" de melhorar a sanidade básica dos insumos.

Contudo , -1 >>> 0 (opa, provavelmente não é o tamanho desejado!)


fonte
0

A amostra do código Java abaixo explica bem:

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));

A saída é a seguinte:

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