Co to jest operator JavaScript >>> i jak go używasz?

150

Patrzyłem na kod z Mozilli, który dodaje metodę filtru do Array i zawiera wiersz kodu, który mnie zdezorientował.

var len = this.length >>> 0;

Nigdy wcześniej nie widziałem >>> używanego w JavaScript.
Co to jest i co robi?

Kenneth J.
źródło
@CMS Prawda, ten kod / pytanie pochodzi z tych; jednak odpowiedzi tutaj są bardziej szczegółowe i wartościowe niż te poprzednie.
Justin Johnson
2
Albo jest to błąd albo ludzie z Mozilli zakładają this.length może wynosić -1. >>> jest operatorem przesunięcia bez znaku, więc var len zawsze będzie wynosić 0 lub więcej.
user347594
1
Ash Searle znalazł dla niego zastosowanie - przewrócił implementację Lorda JS (Doug Crockford) na Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (chociaż zrobił testy, haha).
Dan Beam

Odpowiedzi:

211

Nie tylko konwertuje nie-liczby na liczbę, ale konwertuje je na liczby, które można wyrazić jako 32-bitowe liczby całkowite bez znaku.

Chociaż obsługa JavaScript Liczby są pływaki podwójnej precyzji (*), operatory bitowe ( <<, >>, &, |i ~) są zdefiniowane w kategoriach operacji na 32-bitowych liczb całkowitych. Wykonanie operacji bitowej konwertuje liczbę na 32-bitową liczbę int ze znakiem, tracąc ułamki i bity znajdujące się wyżej niż 32, przed wykonaniem obliczeń, a następnie konwersją z powrotem na Number.

Zatem wykonanie operacji bitowej bez faktycznego efektu, jak przesunięcie w prawo o 0 bitów >>0, jest szybkim sposobem na zaokrąglenie liczby i upewnienie się, że mieści się ona w 32-bitowym zakresie int. Dodatkowo >>>operator potrójny , po wykonaniu operacji bez znaku, konwertuje wyniki swoich obliczeń na liczbę całkowitą bez znaku, a nie na liczbę całkowitą ze znakiem, którą robią inne, więc może być używany do konwersji liczb ujemnych na uzupełnienie do dwóch 32-bitowych wersja jako duża liczba. Użycie >>>0zapewnia, że ​​masz liczbę całkowitą od 0 do 0xFFFFFFFF.

W tym przypadku jest to przydatne, ponieważ ECMAScript definiuje indeksy tablicy w postaci 32-bitowych liczb całkowitych bez znaku. Więc jeśli próbujesz zaimplementować array.filterw sposób, który dokładnie powiela to, co mówi standard ECMAScript Fifth Edition, możesz rzutować liczbę na 32-bitowy int bez znaku w ten sposób.

(W rzeczywistości niewiele jest praktyczna potrzeba to mam nadzieję, że jak ludzie nie będą ustalone array.lengthna 0.5, -1, 1e21lub 'LEMONS'. Ale to autorzy JavaScript mówimy, więc nigdy nie wiadomo ...)

Podsumowanie:

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

(*: cóż, są zdefiniowane jako zachowujące się jak zmiennoprzecinkowe. Nie zdziwiłbym się, gdyby jakiś silnik JavaScript faktycznie używał intów, kiedy to możliwe, ze względu na wydajność. Ale to byłby szczegół implementacji, którego nie można by wziąć zaleta.)

bobince
źródło
2
+2 do dokładnego opisu i tabeli, -1, ponieważ array.length sprawdza się sama i nie może być dowolnie ustawiona na cokolwiek, co nie jest liczbą całkowitą lub 0 (FF zgłasza ten błąd:) RangeError: invalid array length.
Justin Johnson
4
Jednak specyfikacja celowo zezwala na wywoływanie wielu funkcji Array na innych niż Array (np. Via Array.prototype.filter.call), więc w arrayrzeczywistości może nie być rzeczywistą Array: może to być inna klasa zdefiniowana przez użytkownika. (Niestety, nie może to być niezawodnie NodeList, a wtedy naprawdę chciałbyś to zrobić, ponieważ jest to obiekt hosta. Pozostaje to jedyne miejsce, w którym realistycznie to zrobiłbyś jako argumentspseudo-tablica.)
bobince
Świetne wyjaśnienie i świetne przykłady! Niestety to kolejny szalony aspekt Javascript. Po prostu nie rozumiem, co jest tak okropnego w rzucaniu błędu, gdy otrzymujesz niewłaściwy typ. Możliwe jest zezwolenie na dynamiczne pisanie bez dopuszczania każdego przypadkowego błędu do utworzenia rzutowania typu. :(
Mike Williamson
„Użycie >>> 0 zapewnia liczbę całkowitą od 0 do 0xFFFFFFFF.” jak ifwyglądałoby w tym przypadku stwierdzenie, próbując zidentyfikować, że lewa strona oceny nie była int? 'lemons'>>>0 === 0 && 0 >>>0 === 0ocenia jako prawdziwe? chociaż cytryny to oczywiście słowo ..?
Zze
58

Operator przesunięcia w prawo bez znaku jest używany we wszystkich implementacjach metod Mozilli w dodatkach tablicowych , aby zapewnić, że lengthwłaściwość jest 32-bitową liczbą całkowitą bez znaku .

lengthWłasności przedmiotów tablicy jest opisany w opisie jako:

Każdy obiekt Array ma właściwość length, której wartość jest zawsze nieujemną liczbą całkowitą mniejszą niż 2 32 .

Ten operator jest najkrótszą drogą do osiągnięcia tego celu, wewnętrznie metody tablicowe używają ToUint32operacji, ale ta metoda nie jest dostępna i istnieje w specyfikacji dla celów implementacji.

Implementacje dodatków tablicowych Mozilli starają się być zgodne z ECMAScript 5 , spójrz na opis Array.prototype.indexOfmetody (§ 15.4.4.14):

1. Niech O będzie wynikiem wywołania ToObject z przekazaniem tej wartości 
   jako argument.
2. Niech lenValue będzie wynikiem wywołania wewnętrznej metody [[Get]] O with 
   argument „długość”.
3. Niech len be ToUint32 (lenValue) .
....

Jak widać, chcą po prostu odtworzyć zachowanie ToUint32metody, aby zachować zgodność ze specyfikacją ES5 w implementacji ES3, a jak powiedziałem wcześniej, operator przesunięcia w prawo bez znaku jest najłatwiejszy.

CMS
źródło
Chociaż implementacja dodatków do tablicy połączonej może być poprawna (lub bliska poprawności), kod nadal jest złym przykładem kodu. Być może nawet komentarz wyjaśniający zamiar rozwiązałby tę sytuację.
fmark
2
Czy to możliwe, że długość tablicy nie jest liczbą całkowitą? Nie mogę sobie tego wyobrazić, więc tego rodzaju ToUint32wydaje mi się trochę niepotrzebne.
Marcel Korpel
7
@Marcel: Należy pamiętać, że większość Array.prototypemetod jest celowo ogólna , można ich używać na obiektach przypominających tablice, np Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. argumentsObiekt jest również dobrym przykładem. W przypadku obiektów czystych tablic nie można zmienić typu lengthwłaściwości, ponieważ implementują one specjalną metodę wewnętrzną [[Put ]], a po przypisaniu do lengthwłaściwości jest ponownie konwertowana ToUint32i podejmowane są inne działania, takie jak usuwanie indeksów powyżej nowa długość ...
CMS
32

To jest operator przesunięcia bitu w prawo bez znaku . Różnica między tym i podpisanym odpowiednim operatorem nieco zmianowej , jest to, że bez znaku prawo operator bitowy shift ( >>> ) wypełnia zerami z lewej strony, a podpisane tuż nieco operator przesunięcia ( >> ) wypełnia z bitem znaku, a tym samym zachowanie znaku wartości liczbowej po przesunięciu.

driis
źródło
Ivan, to przesunęłoby go o 0 miejsc; to stwierdzenie niczego by nie zmieniło.
Dean J
3
@Ivan, normalnie powiedziałbym, że przesuwanie wartości o zero miejsc nie ma absolutnie żadnego sensu. Ale to jest JavaScript, więc może kryć się za tym znaczenie. Nie jestem guru Javascript, ale może to być sposób na upewnienie się, że wartość jest w rzeczywistości liczbą całkowitą w języku Javasacript bez typu.
driis
2
@Ivan, zobacz odpowiedź Justina poniżej. W rzeczywistości jest to sposób na zapewnienie, że zmienna len zawiera liczbę.
driis
1
Ponadto >>>konwertuje na liczbę całkowitą, której jednoargumentowy +nie robi.
rekurencyjny
this.length >>> 0 konwertuje liczbę całkowitą ze znakiem na liczbę bez znaku. Osobiście uważam, że jest to przydatne podczas ładowania pliku binarnego z niepodpisanymi intami.
Matt Parkins,
29

Driis wystarczająco wyjaśnił, czym jest operator i co robi. Oto znaczenie tego / dlaczego został użyty:

Przesunięcie w dowolnym kierunku o 0zwraca oryginalną liczbę i spowoduje rzut nullna 0. Wygląda na to, że przykładowy kod, na który patrzysz, używa this.length >>> 0do zapewnienia, że lenjest numeryczny, nawet jeśli this.lengthnie jest zdefiniowany.

Dla wielu ludzi operacje bitowe są niejasne (a Douglas Crockford / jslint sugeruje, aby nie używać takich rzeczy). Nie oznacza to, że jest to złe, ale istnieją bardziej korzystne i znane metody, które czynią kod bardziej czytelnym. Bardziej przejrzysty sposób na upewnienie się, że lenjest 0to jedna z dwóch poniższych metod.

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

lub

// 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
źródło
1
Chociaż twoje drugie rozwiązanie czasami dawałoby wynik NaN... Np. +{}... Prawdopodobnie najlepiej połączyć te dwa rozwiązania:+length||0
James
1
this.length znajduje się w kontekście obiektu tablicy, który nie może być niczym innym niż nieujemną liczbą całkowitą (przynajmniej w FF), więc nie ma takiej możliwości. Ponadto {} || 1 zwraca {}, więc nie jest lepiej, jeśli this.length jest obiektem. Zaletą rzutowania jednoargumentowego this.length w pierwszej metodzie jest to, że obsługuje ona przypadki, w których this.length ma wartość NaN. Zredagowana odpowiedź, aby to odzwierciedlić.
Justin Johnson
jslint również narzekałby na var len = + this.length jako na „mylące plusy”. Douglas, jesteś taki wybredny!
Bayard Randel
Douglas jest wybredny. I chociaż jego argumenty są mądre i zazwyczaj dobrze uzasadnione, to, co mówi, nie jest absolutne ani ewangeliczne.
Justin Johnson,
15

>>>jest podpisany operatora przesunięcie w prawo ( patrz str. 76 opisu JavaScript 1,5 ), w przeciwieństwie do >>, na podpisany odpowiedni przesunięcia bitowego.

>>>zmienia wyniki przesuwania liczb ujemnych, ponieważ nie zachowuje bitu znaku podczas przesuwania . Konsekwencje tego można zrozumieć na przykładzie tłumacza:

$ 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

Więc to, co prawdopodobnie ma zostać tutaj zrobione, to uzyskać długość lub 0, jeśli długość jest niezdefiniowana lub nie jest liczbą całkowitą, jak w "cabbage"powyższym przykładzie. Myślę, że w tym przypadku można bezpiecznie założyć, że this.lengthnigdy nie będzie < 0. Niemniej jednak argumentowałbym, że ten przykład to paskudny hack z dwóch powodów:

  1. Zachowanie w <<<przypadku używania liczb ujemnych, efekt uboczny prawdopodobnie nie zamierzony (lub prawdopodobnie nie wystąpi) w powyższym przykładzie.

  2. Zamiar kodu nie jest oczywisty , o czym świadczy istnienie tego pytania.

Najlepszą praktyką jest prawdopodobnie użycie czegoś bardziej czytelnego, chyba że wydajność jest absolutnie krytyczna:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
źródło
Więc ... @johncatfish jest poprawne? Ma to na celu zapewnienie, że długość jest nieujemna?
Anthony
4
Czy taki przypadek mógłby się -1 >>> 0kiedykolwiek zdarzyć, a jeśli tak, to czy naprawdę pożądane jest przesunięcie go do 4294967295? Wydaje się, że spowodowałoby to uruchomienie pętli kilka razy więcej niż to konieczne.
deceze
@deceze: Bez zobaczenia implementacji this.lengthnie można tego wiedzieć. Dla każdej "rozsądnej" implementacji długość łańcucha nigdy nie powinna być ujemna, ale wtedy można by argumentować, że w "rozsądnym" środowisku możemy założyć, że istnieje this.lengthwłaściwość, która zawsze zwraca liczbę całkowitą.
fmark
mówisz, że >>> nie zachowuje bitu znaku .. ok .. Więc musiałbym zapytać, kiedy mamy do czynienia z liczbami ujemnymi .. przed jakąkolwiek >>> lub >> konwersją, czy są one zgodne z 2s czy też są w postaci liczb całkowitych ze znakiem i skąd będziemy wiedzieć? Swoją drogą, chyba nie mówi się, że dopełnienie 2s ma bit znaku .. jest to alternatywa dla zapisu ze znakiem, ale można wyznaczyć znak liczby całkowitej
barlop
10

Dwa powody:

  1. Wynik >>> jest „całką”

  2. undefined >>> 0 = 0 (ponieważ JS spróbuje wymusić LFS na kontekst numeryczny, zadziała to również dla "foo" >>> 0 itd.)

Pamiętaj, że liczby w JS mają wewnętrzną reprezentację liczby podwójnej. Jest to po prostu „szybki” sposób podstawowego zachowania rozsądku pod względem długości.

Jednak -1 >>> 0 (ups, prawdopodobnie nie jest to pożądana długość!)


źródło
0

Przykładowy kod Java poniżej dobrze wyjaśnia:

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

Wynik jest następujący:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
nitinsridar
źródło