Was ist der JavaScript >>> -Operator und wie verwenden Sie ihn?

149

Ich habe mir Code von Mozilla angesehen, der Array eine Filtermethode hinzufügt, und er hatte eine Codezeile, die mich verwirrte.

var len = this.length >>> 0;

Ich habe noch nie >>> in JavaScript verwendet gesehen.
Was ist das und was macht es?

Kenneth J.
quelle
@CMS Richtig, dieser Code / diese Frage stammt von denen; Die Antworten hier sind jedoch spezifischer und wertvoller als die vorherigen.
Justin Johnson
2
Oder es ist ein Fehler oder Mozilla-Leute gehen davon aus, dass dies die Länge -1 sein könnte. >>> ist ein vorzeichenloser Schichtoperator, daher ist var len immer 0 oder größer.
user347594
1
Ash Searle fand eine Verwendung dafür - er stürzte die Implementierung von Lord of JS (Doug Crockford) auf Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (obwohl er die Tests durchgeführt hat, haha).
Dan Beam

Antworten:

211

Nicht-Zahlen werden nicht nur in Zahlen konvertiert, sondern auch in Zahlen, die als vorzeichenlose 32-Bit-Ints ausgedrückt werden können.

Obwohl JavaScript die Zahlen mit doppelter Genauigkeit Schwimmer (*) sind die Bit - Operatoren ( <<, >>, &, |und ~) auf 32-Bit - Integer in Bezug auf die Operationen definiert. Durch eine bitweise Operation wird die Zahl in ein 32-Bit-Int mit Vorzeichen konvertiert, wobei Brüche und höherwertige Bits als 32 verloren gehen, bevor die Berechnung durchgeführt und dann wieder in Zahl konvertiert wird.

Eine bitweise Operation ohne tatsächlichen Effekt, wie eine Verschiebung von 0 Bits nach rechts >>0, ist also eine schnelle Möglichkeit, eine Zahl zu runden und sicherzustellen, dass sie im 32-Bit-Int-Bereich liegt. Darüber hinaus >>>konvertiert der Dreifachoperator nach seiner vorzeichenlosen Operation die Ergebnisse seiner Berechnung als vorzeichenlose Ganzzahl in die vorzeichenbehaftete Ganzzahl und nicht in die vorzeichenbehaftete Ganzzahl, sodass die Negative in das 32-Bit-Zwei-Komplement konvertiert werden können Version als große Zahl. Durch >>>0die Verwendung wird sichergestellt, dass Sie eine Ganzzahl zwischen 0 und 0xFFFFFFFF haben.

In diesem Fall ist dies nützlich, da ECMAScript Array-Indizes in Form von 32-Bit-Ints ohne Vorzeichen definiert. Wenn Sie also versuchen, array.filterauf eine Weise zu implementieren , die genau das dupliziert, was der ECMAScript Fifth Edition-Standard sagt, würden Sie die Zahl wie folgt in 32-Bit-Int ohne Vorzeichen umwandeln.

(In Wirklichkeit gibt es wenig praktische Notwendigkeit dafür , wie hoffentlich die Menschen gehen nicht einstellen werden , array.lengthum 0.5, -1, 1e21oder 'LEMONS'. Aber das ist JavaScript Autoren wir reden, so dass Sie nie wissen ...)

Zusammenfassung:

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

(*: Nun, sie verhalten sich wie Floats. Es würde mich nicht wundern, wenn eine JavaScript-Engine aus Leistungsgründen tatsächlich Ints verwenden würde, wenn dies möglich wäre. Aber das wäre ein Implementierungsdetail, das Sie nicht benötigen würden Vorteil von.)

Bobince
quelle
2
+2 in der Tiefenbeschreibung und Tabelle, -1, da array.length sich selbst validiert und nicht willkürlich auf etwas gesetzt werden kann, das keine Ganzzahl oder 0 ist (FF löst diesen Fehler aus :) RangeError: invalid array length.
Justin Johnson
4
Die Spezifikation erlaubt jedoch absichtlich, dass viele Array-Funktionen für Nicht-Arrays aufgerufen werden (z. B. über Array.prototype.filter.call), sodass dies arraymöglicherweise nicht real ist Array: Es kann sich um eine andere benutzerdefinierte Klasse handeln. (Leider kann es nicht zuverlässig eine NodeList sein, wenn Sie das wirklich wollen, da dies ein Host-Objekt ist. Damit bleibt der einzige Ort, an dem Sie dies realistisch als argumentsPseudo-Array tun würden .)
Bobince
Tolle Erklärung und tolle Beispiele! Leider ist dies ein weiterer verrückter Aspekt von Javascript. Ich verstehe einfach nicht, was so schrecklich daran ist, einen Fehler zu werfen, wenn Sie den falschen Typ erhalten. Es ist möglich, dynamisches Schreiben zuzulassen, ohne dass bei jedem versehentlichen Fehler ein Typ-Casting erstellt wird. :(
Mike Williamson
"Wenn Sie >>> 0 verwenden, wird sichergestellt, dass Sie eine Ganzzahl zwischen 0 und 0xFFFFFFFF haben." Wie würde die ifAussage dafür aussehen, wenn versucht wird festzustellen, dass die linke Seite der Bewertung kein Int ist? 'lemons'>>>0 === 0 && 0 >>>0 === 0bewertet als wahr? obwohl Zitronen offensichtlich ein Wort ist ..?
Zze
58

Der vorzeichenlose Rechtsverschiebungsoperator wird in allen Methodenimplementierungen des Array-Extra von Mozilla verwendet, um sicherzustellen, dass die lengthEigenschaft eine vorzeichenlose 32-Bit-Ganzzahl ist .

Die lengthEigenschaft von Array-Objekten wird in der Spezifikation wie folgt beschrieben :

Jedes Array-Objekt hat eine Längeneigenschaft, deren Wert immer eine nichtnegative Ganzzahl kleiner als 2 32 ist .

Dieser Operator ist der kürzeste Weg, um dies zu erreichen. Interne Array-Methoden verwenden die ToUint32Operation, aber diese Methode ist nicht verfügbar und existiert in der Spezifikation für Implementierungszwecke.

Die Mozilla- Array-Extras- Implementierungen versuchen, ECMAScript 5- kompatibel zu sein. Array.prototype.indexOfBeachten Sie die Beschreibung der Methode (§ 15.4.4.14):

1. Sei O das Ergebnis des Aufrufs von ToObject, wobei dieser Wert übergeben wird 
   als Argument.
2. Sei lenValue das Ergebnis des Aufrufs der internen Methode [[Get]] von O mit 
   das Argument "Länge".
3. Sei len ToUint32 (lenValue) .
....

Wie Sie sehen können, möchten sie nur das Verhalten der ToUint32Methode reproduzieren , um der ES5-Spezifikation für eine ES3-Implementierung zu entsprechen, und wie ich bereits sagte, ist der vorzeichenlose Rechtsschichtoperator der einfachste Weg.

CMS
quelle
Während die Implementierung der Extras für verknüpfte Arrays möglicherweise korrekt ist (oder nahezu korrekt ist), ist der Code immer noch ein schlechtes Codebeispiel. Vielleicht würde sogar ein Kommentar zur Klärung der Absicht diese Situation lösen.
Mark
2
Ist es möglich, dass die Länge eines Arrays keine Ganzzahl ist? Ich kann mir das nicht vorstellen, deshalb ToUint32scheint mir diese Art von etwas unnötig.
Marcel Korpel
7
@Marcel: Beachten Sie, dass die meisten Array.prototypeMethoden absichtlich generisch sind und für Array-ähnliche Objekte verwendet werden können, z Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. Das argumentsObjekt ist auch ein gutes Beispiel. Bei reinen Array-Objekten ist es unmöglich, den Typ der lengthEigenschaft zu ändern , da sie eine spezielle interne [[Put ]] Methode implementieren. Wenn eine Zuweisung zur lengthEigenschaft vorgenommen wird, wird diese erneut konvertiert ToUint32und es werden andere Aktionen ausgeführt, z. B. das Löschen der obigen Indizes die neue Länge ...
CMS
32

Das ist der vorzeichenlose Rechtsbitverschiebungsoperator . Der Unterschied zwischen diesem und dem vorzeichenbehafteten Rechtsbitverschiebungsoperator besteht darin, dass der vorzeichenlose Rechtsbitverschiebungsoperator ( >>> ) mit Nullen von links und der vorzeichenbehaftete Rechtsbitverschiebungsoperator ( >> ) mit dem Vorzeichenbit gefüllt wird Beibehalten des Vorzeichens des numerischen Werts beim Verschieben.

driis
quelle
Ivan, das würde es um 0 Stellen verschieben; Diese Aussage würde nichts ändern.
Dean J
3
@Ivan, normalerweise würde ich sagen, dass es absolut keinen Sinn macht, einen Wert um null Stellen zu verschieben. Aber das ist Javascript, also könnte es eine Bedeutung dahinter geben. Ich bin kein Javascript-Guru, aber es könnte eine Möglichkeit sein, sicherzustellen, dass der Wert tatsächlich eine Ganzzahl in der typenlosen Javasacript-Sprache ist.
Driis
2
@ Ivan, siehe Justins Antwort unten. Auf diese Weise können Sie sicherstellen, dass die Variable len eine Zahl enthält.
Driis
1
Außerdem >>>wird in eine Ganzzahl konvertiert, was unary +nicht tut.
rekursiv
this.length >>> 0 konvertiert eine vorzeichenbehaftete Ganzzahl in eine vorzeichenlose. Persönlich fand ich dies nützlich, wenn ich eine Binärdatei mit vorzeichenlosen Ints lade.
Matt Parkins
29

Driis hat ausreichend erklärt, was der Bediener ist und was er tut. Hier ist die Bedeutung dahinter / warum es verwendet wurde:

Shifting durch jede Richtung 0tut kehrt die Zahl wieder und werfen nullzu 0. Es scheint, dass der Beispielcode, den Sie betrachten, verwendet wird, this.length >>> 0um sicherzustellen, dass er lennumerisch ist, auch wenn er this.lengthnicht definiert ist.

Für viele Menschen sind bitweise Operationen unklar (und Douglas Crockford / jslint schlägt vor, solche Dinge nicht zu verwenden). Das bedeutet nicht, dass es falsch ist, aber es gibt günstigere und bekanntere Methoden, um Code lesbarer zu machen. Ein klarerer Weg, um dies sicherzustellen, lenist 0eine der beiden folgenden Methoden.

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

oder

// 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
quelle
1
Obwohl Ihre zweite Lösung manchmal zu ... bewertet wird NaN. ZB +{}... Es ist wahrscheinlich am besten, die beiden zu kombinieren:+length||0
James
1
Diese Länge steht im Kontext des Array-Objekts, das nichts anderes als eine nicht negative Ganzzahl sein kann (zumindest in FF), daher ist dies hier nicht möglich. Auch {} || 1 gibt {} zurück, sodass Sie nicht besser dran sind, wenn this.length ein Objekt ist. Der Vorteil eines ebenfalls unären Gießens dieser Länge bei der ersten Methode besteht darin, dass Fälle behandelt werden, in denen diese Länge NaN ist. Die Antwort wurde bearbeitet, um dies widerzuspiegeln.
Justin Johnson
jslint würde sich auch über var len = + this.length als "verwirrende Plusses" beschweren. Douglas, du bist so wählerisch!
Bayard Randel
Douglas ist wählerisch. Und während seine Argumente weise und typisch begründet sind, ist das, was er sagt, weder absolut noch evangelisch.
Justin Johnson
15

>>>das ist unsigned rechtser Verschiebungsoperator ( vgl . S. 76 der JavaScript 1.5 - Spezifikation ), im Gegensatz zu dem >>, dem signierten rechten Shift - Operator.

>>>Ändert die Ergebnisse der Verschiebung negativer Zahlen, da das Vorzeichenbit beim Verschieben nicht erhalten bleibt . Die Konsequenzen davon können anhand eines Dolmetschers anhand eines Beispiels verstanden werden:

$ 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

Was hier wahrscheinlich getan werden soll, ist, die Länge oder 0 zu erhalten, wenn die Länge undefiniert ist oder keine ganze Zahl, wie im "cabbage"obigen Beispiel. Ich denke in diesem Fall ist es sicher anzunehmen, dass dies this.lengthniemals der Fall sein wird < 0. Trotzdem würde ich argumentieren, dass dieses Beispiel aus zwei Gründen ein böser Hack ist:

  1. Das Verhalten <<<bei Verwendung negativer Zahlen ist ein Nebeneffekt, der im obigen Beispiel wahrscheinlich nicht beabsichtigt ist (oder wahrscheinlich auftritt).

  2. Die Absicht des Codes ist nicht offensichtlich , wie die Existenz dieser Frage bestätigt.

Die beste Vorgehensweise besteht wahrscheinlich darin, etwas Lesbareres zu verwenden, es sei denn, die Leistung ist absolut kritisch:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
quelle
Sooo ... @johncatfish ist richtig? Es soll sicherstellen, dass diese Länge nicht negativ ist?
Anthony
4
Könnte der Fall -1 >>> 0jemals eintreten und wenn ja, ist es wirklich wünschenswert, ihn auf 4294967295 zu verschieben? Scheint so, als würde die Schleife einige Male öfter als nötig ausgeführt.
Täuschung
@deceze: Ohne die Implementierung zu sehen this.length, ist es unmöglich zu wissen. Für jede "vernünftige" Implementierung sollte die Länge eines Strings niemals negativ sein, aber dann könnte man argumentieren, dass wir in einer "vernünftigen" Umgebung die Existenz einer this.lengthEigenschaft annehmen können, die immer eine ganzzahlige Zahl zurückgibt.
Mark
Sie sagen, >>> bewahrt das Vorzeichenbit nicht .. ok .. Also, ich muss fragen, wenn wir uns mit negativen Zahlen befassen .. vor jeder >>> oder >> Konvertierung, sind sie in 2s-Komplement Form, oder sind sie in vorzeichenbehafteter Ganzzahlform, und wie würden wir wissen? Übrigens, 2s Komplement, von dem ich denke, dass es vielleicht kein Vorzeichenbit gibt. Es ist eine Alternative zur vorzeichenbehafteten Notation, aber es ist möglich, das Vorzeichen einer Ganzzahl zu bestimmen
Barlop
10

Zwei Gründe:

  1. Das Ergebnis von >>> ist ein "Integral"

  2. undefined >>> 0 = 0 (da JS versucht, das LFS in einen numerischen Kontext zu zwingen, funktioniert dies auch für "foo" >>> 0 usw.)

Denken Sie daran, dass Zahlen in JS eine interne Darstellung von double haben. Es ist nur ein "schneller" Weg, um die Länge der Eingabe zu verbessern.

Jedoch -1 >>> 0 (oops, wahrscheinlich keine gewünschte Länge!)


quelle
0

Der folgende Java-Beispielcode erklärt gut:

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

Die Ausgabe ist die folgende:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
Nitinsridar
quelle