Cos'è l'operatore JavaScript >>> e come lo usi?


150

Stavo guardando il codice di Mozilla che aggiungeva un metodo di filtro ad Array e aveva una riga di codice che mi confondeva.

var len = this.length >>> 0;

Non ho mai visto >>> usato in JavaScript prima.
Cos'è e che cosa fa?


@CMS Vero, questo codice / domanda proviene da quelli; tuttavia, la risposta qui è più specifica e preziosa di quelle precedenti.
Justin Johnson,

2
Oppure è un bug o i ragazzi di Mozilla presumono che this.length potrebbe essere -1. >>> è un operatore a turni senza segno, quindi varenen sarà sempre 0 o maggiore.
user347594,

1
Ash Searle ne trovò utile - ribaltando il signore dell'implementazione di JS (Doug Crockford) a Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (anche se ha fatto i test, haha).
Dan Beam,

Risposte:


212

Non si limita a convertire non numeri in numeri, li converte in numeri che possono essere espressi come ints senza segno a 32 bit.

Anche se i numeri di JavaScript sono carri a precisione doppia (*), gli operatori bit per bit ( <<, >>, &, |e~ ) sono definiti in termini di operazioni su interi a 32 bit. Se si esegue un'operazione bit a bit, il numero viene convertito in un int con segno a 32 bit, perdendo tutte le frazioni e i bit di posizione superiore a 32, prima di eseguire il calcolo e quindi riconvertire in Numero.

Quindi fare un'operazione bit a bit senza alcun effetto reale, come uno spostamento a destra di 0 bit >>0, è un modo rapido per arrotondare un numero e assicurarsi che sia nell'intervallo di 32 bit. Inoltre, l' >>>operatore triplo , dopo aver eseguito l'operazione senza segno, converte i risultati del suo calcolo in Numero come numero intero senza segno anziché come numero intero con segno che fanno gli altri, quindi può essere utilizzato per convertire i negativi nel complemento a 32 bit-due versione come un grande numero. utilizzando>>>0 assicura di avere un numero intero compreso tra 0 e 0xFFFFFFFF.

In questo caso ciò è utile perché ECMAScript definisce gli indici di array in termini di ints senza segno a 32 bit. Quindi, se stai cercando di implementare array.filterin un modo che duplica esattamente ciò che dice lo standard ECMAScript Fifth Edition, lanci il numero su int non firmato a 32 bit in questo modo.

(In realtà c'è poco necessità pratica di questo come spero che la gente non stanno andando essere l'impostazione array.lengtha 0.5, -1, 1e21o 'LEMONS'. Ma questo è autori JavaScript di cui stiamo parlando, in modo da non si sa mai ...)

Sommario:

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

(*: beh, sono definiti comportarsi come float. Non mi sorprenderebbe se qualche motore JavaScript usasse effettivamente ints quando poteva, per motivi di prestazioni. Ma sarebbe un dettaglio di implementazione che non potresti prendere vantaggio di.)


2
+2 descrizione e tabella approfondite, -1 perché array.length si convalida e non può essere impostato arbitrariamente su nulla che non sia un numero intero o 0 (FF genera questo errore:) RangeError: invalid array length.
Justin Johnson,

4
Tuttavia, la specifica consente deliberatamente a molte funzioni di array di essere chiamate su non-array (ad es. Via Array.prototype.filter.call), quindi arraypotrebbe non essere effettivamente reale Array: potrebbe trattarsi di un'altra classe definita dall'utente. (Sfortunatamente, non può essere in modo affidabile un NodeList, ovvero quando si vorrebbe davvero farlo, poiché si tratta di un oggetto host. Ciò lascia l'unico posto in cui lo si farebbe realisticamente come lo argumentspseudo-array.)
bobince

Grande spiegazione e grandi esempi! Purtroppo questo è un altro aspetto folle di Javascript. Non capisco cosa sia così orribile nel lanciare un errore quando ricevi il tipo sbagliato. È possibile consentire la digitazione dinamica senza consentire ad ogni errore accidentale di creare un casting di tipo. :(
Mike Williamson,

"L'uso di >>> 0 assicura che tu abbia un numero intero compreso tra 0 e 0xFFFFFFFF." come sarebbe la ifdichiarazione per questo quando si cerca di identificare che il lato sinistro della valutazione non è un int? 'lemons'>>>0 === 0 && 0 >>>0 === 0valuta come vero? anche se limoni è ovviamente una parola ..?
Zze,

58

L'operatore di spostamento a destra senza segno viene utilizzato nelle implementazioni di tutti i metodi extra di Mozilla per garantire che la lengthproprietà sia un numero intero a 32 bit senza segno .

La lengthproprietà degli oggetti array è descritta nelle specifiche come:

Ogni oggetto Array ha una proprietà length il cui valore è sempre un numero intero non negativo inferiore a 2 32 .

Questo operatore è il modo più breve per raggiungerlo, i metodi di array interni utilizzano l' ToUint32operazione, ma quel metodo non è accessibile ed esiste sulla specifica a fini di implementazione.

Le implementazioni degli extra di array Mozilla cercano di essere conformi a ECMAScript 5 , guarda la descrizione del Array.prototype.indexOfmetodo (§ 15.4.4.14):

1. Sia O il risultato della chiamata a ToObject che passa questo valore 
   come argomento.
2. Lascia che lenValue sia il risultato della chiamata al metodo interno [[Get]] di O con 
   l'argomento "lunghezza".
3. Let len ​​be ToUint32 (lenValue) .
....

Come puoi vedere, vogliono solo riprodurre il comportamento del ToUint32metodo per conformarsi alle specifiche ES5 su un'implementazione ES3 e, come ho detto prima, l' operatore di turno destro senza segno è il modo più semplice.


Mentre l' implementazione degli extra di array collegati può essere corretta (o vicina alla correzione), il codice è ancora un esempio di codice errato. Forse anche un commento per chiarire l'intenzione risolverebbe questa situazione.
fmark

2
È possibile che la lunghezza di un array non sia un numero intero? Non riesco a immaginarlo, quindi questo tipo di mi ToUint32sembra un po 'inutile.
Marcel Korpel,

7
@Marcel: tieni presente che la maggior parte dei Array.prototypemetodi sono intenzionalmente generici , possono essere usati su oggetti simili ad arrayArray.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1; . Anche l' argumentsoggetto è un buon esempio. Per oggetti array puri , è impossibile cambiare il tipo di lengthproprietà, poiché implementano un metodo interno [[Put ]] speciale e quando viene effettuata un'assegnazione alla lengthproprietà, viene nuovamente convertito ToUint32e vengono intraprese altre azioni, come l'eliminazione degli indici sopra la nuova lunghezza ...
CMS,

32

Questo è l' operatore di spostamento bit destro senza segno . La differenza tra questo e l' operatore di spostamento a destra bit firmato , è che il senza segno a destra dell'operatore po 'di spostamento ( >>> ) riempie di zeri da sinistra, e il sottoscritto po' operatore spostamento a destra ( >> ) si riempie del bit di segno, in tal modo preservando il segno del valore numerico quando spostato.


Ivan, lo sposterebbe di 0 posti; questa affermazione non cambierebbe nulla.
Decano J

3
@Ivan, normalmente, direi che spostare un valore di zero posizioni non ha assolutamente senso. Ma questo è Javascript, quindi potrebbe esserci un significato dietro di esso. Non sono un guru Javascript, ma potrebbe essere un modo per garantire che il valore sia in realtà un numero intero nel linguaggio Javasacript senza testo.
driis

2
@Ivan, vedi la risposta di Justin di seguito. È in effetti un modo per garantire che la variabile len contenga un numero.
driis,

1
Inoltre, >>>converte in un numero intero, cosa che unario +non fa.
ricorsivo il

this.length >>> 0 converte un numero intero con segno in uno senza segno. Personalmente l'ho trovato utile durante il caricamento di un file binario con ints non firmati.
Matt Parkins,

29

Driis ha sufficientemente spiegato cosa sia l'operatore e cosa fa. Ecco il significato dietro / perché è stato usato:

Spostando qualsiasi direzione 0non restituisce il numero originale e lancerà nulla 0. Sembra che il codice di esempio che stai guardando stia utilizzando this.length >>> 0per garantire che lensia numerico anche se this.lengthnon è definito.

Per molte persone, le operazioni bit per bit non sono chiare (e Douglas Crockford / jslint suggerisce di non usare tali cose). Ciò non significa che sia sbagliato, ma esistono metodi più favorevoli e familiari per rendere il codice più leggibile. Un modo più chiaro per garantire che lenè 0è uno dei seguenti due metodi.

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

1
Sebbene, la tua seconda soluzione a volte valuti per NaN... Ad esempio +{}... Probabilmente è meglio combinare i due:+length||0
James

1
this.length è nel contesto dell'oggetto array, che non può essere altro che un numero intero non negativo (almeno in FF), quindi qui non è possibile. Inoltre, {} || 1 restituisce {} quindi non stai meglio se this.length è un oggetto. Il vantaggio di lanciare anche unario this.length nel primo metodo è che gestisce i casi in cui this.length è NaN. Risposta modificata per riflettere ciò.
Justin Johnson,

jslint si lamenterebbe di var len = + this.length anche come "argomenti confusi". Douglas, sei così esigente!
Bayard Randel,

Douglas è schizzinoso. E mentre i suoi argomenti sono saggi e tipicamente ben fondati, ciò che dice non è assoluto né evangelico.
Justin Johnson

15

>>> è il senza segno operatore di spostamento a destra ( vedi pag. 76 della specifica JavaScript 1.5 ), in contrasto con il>>, il sottoscritto operatore spostamento a destra.

>>>cambia i risultati dello spostamento dei numeri negativi perché non conserva il bit di segno durante lo spostamento . Le conseguenze di ciò sono comprensibili per esempio da un interprete:

$ 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

Quindi ciò che probabilmente si intende fare qui è ottenere la lunghezza, o 0 se la lunghezza non è definita o non è un numero intero, come "cabbage"nell'esempio sopra. Penso che in questo caso sia sicuro supporre che this.lengthnon lo sarà mai < 0. Tuttavia, direi che questo esempio è un brutto trucco , per due motivi:

  1. Il comportamento di <<<quando si usano numeri negativi, un effetto collaterale probabilmente non è previsto (o probabilmente si verificherà) nell'esempio sopra.

  2. L'intenzione del codice non è ovvia , come dimostra l'esistenza di questa domanda.

La migliore pratica è probabilmente quella di utilizzare qualcosa di più leggibile a meno che le prestazioni non siano assolutamente critiche:

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

Sooo ... @johncatfish è corretto? È per garantire che this.length non sia negativo?
Anthony,

4
Il caso potrebbe -1 >>> 0mai accadere e, in tal caso, è davvero desiderabile spostarlo su 4294967295? Sembra che ciò causerebbe l'esecuzione del ciclo un paio di volte in più del necessario.
Inganno

@deceze: senza vederne l'implementazione this.lengthè impossibile saperlo. Per qualsiasi implementazione "sana" la lunghezza di una stringa non dovrebbe mai essere negativa, ma si potrebbe sostenere che in un ambiente "sano" si può supporre l'esistenza di una this.lengthproprietà che restituisce sempre un numero integrale.
fmark

dici >>> non preserva il bit del segno .. ok .. Quindi, dovrei chiedere, quando abbiamo a che fare con numeri negativi .. prima di qualsiasi conversione >>> o >>, sono nel complemento 2s forma, o sono in forma intera con segno, e come lo sapremmo? A proposito, il complemento a 2s penso che forse non si dice che abbia un bit di segno .. è un'alternativa alla notazione firmata, ma è possibile determinare il segno di un numero intero
barlop

10

Due motivi:

  1. Il risultato di >>> è un "integrale"

  2. undefined >>> 0 = 0 (poiché JS proverà a forzare l'LFS in un contesto numerico, funzionerà anche per "pippo" >>> 0, ecc.)

Ricorda che i numeri in JS hanno una rappresentazione interna del doppio. È solo un modo "rapido" di sanità mentale di input di base per la lunghezza.

Tuttavia , -1 >>> 0 (oops, probabilmente non la lunghezza desiderata!)


0

Il seguente codice Java di esempio spiega bene:

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

L'output è il seguente:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.