Apa itu operator JavaScript >>> dan bagaimana Anda menggunakannya?

150

Saya melihat kode dari Mozilla yang menambahkan metode filter ke Array dan ada sederet kode yang membingungkan saya.

var len = this.length >>> 0;

Saya belum pernah melihat >>> digunakan dalam JavaScript sebelumnya.
Apa itu dan apa fungsinya?

Kenneth J
sumber
@CMS Benar, kode / pertanyaan ini berasal dari mereka; namun, tanggapan di sini lebih spesifik dan berharga daripada yang sebelumnya.
Justin Johnson
2
Atau itu bug atau Mozilla yang mengasumsikan ini. Panjangnya bisa -1. >>> adalah operator shift yang tidak ditandatangani sehingga var len akan selalu 0 atau lebih besar.
user347594
1
Ash Searle menemukan kegunaannya - menjungkirbalikkan penerapan JS (Doug Crockford) ke Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (meskipun ia melakukan tes, haha).
Dan Beam

Jawaban:

212

Itu tidak hanya mengkonversi non-Angka ke Angka, itu mengubahnya menjadi Angka yang dapat dinyatakan sebagai ints 32-bit unsigned.

Meskipun Nomor JavaScript ini adalah double-presisi mengapung (*), operator bitwise ( <<, >>, &, |dan ~) didefinisikan dalam hal operasi pada 32-bit bilangan bulat. Melakukan operasi bitwise mengubah angka menjadi int yang ditandatangani 32-bit, kehilangan pecahan dan bit tempat yang lebih tinggi dari 32, sebelum melakukan perhitungan dan kemudian mengonversi kembali ke Angka.

Jadi melakukan operasi bitwise tanpa efek aktual, seperti pergeseran ke kanan 0 bit >>0, adalah cara cepat untuk membulatkan angka dan memastikannya berada dalam kisaran int 32-bit. Selain itu, >>>operator rangkap tiga , setelah melakukan operasi yang tidak ditandatangani, mengubah hasil perhitungannya menjadi Angka sebagai bilangan bulat yang tidak ditandatangani daripada bilangan bulat yang ditandatangani yang lain, sehingga dapat digunakan untuk mengonversi negatif ke komplemen 32-bit-dua-dua-komplemen. versi sebagai Nomor besar. Menggunakan >>>0memastikan Anda memiliki bilangan bulat antara 0 dan 0xFFFFFFFF.

Dalam hal ini ini berguna karena ECMAScript mendefinisikan indeks Array dalam hal 32 bit int unsigned. Jadi, jika Anda mencoba menerapkan array.filterdengan cara yang persis duplikat apa yang dikatakan standar ECMAScript Fifth Edition, Anda akan memasukkan nomor ke int unsigned 32-bit seperti ini.

(Pada kenyataannya ada sedikit kebutuhan praktis untuk ini karena mudah-mudahan orang tidak akan menetapkan array.lengthuntuk 0.5, -1, 1e21atau 'LEMONS'. Tapi ini penulis JavaScript kita bicarakan, sehingga Anda tidak pernah tahu ...)

Ringkasan:

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

(*: yah, mereka didefinisikan berperilaku seperti pelampung. Tidak akan mengejutkan saya jika beberapa mesin JavaScript benar-benar menggunakan ints ketika itu bisa, karena alasan kinerja. Tapi itu akan menjadi detail implementasi yang tidak akan Anda ambil keuntungan dari.)

bobince
sumber
2
+2 dalam deskripsi dan tabel mendalam, -1 karena array.length memvalidasi dirinya sendiri dan tidak dapat secara sewenang-wenang diatur ke apa pun yang bukan bilangan bulat atau 0 (FF melempar kesalahan ini:) RangeError: invalid array length.
Justin Johnson
4
Namun, spek tersebut sengaja memungkinkan banyak fungsi Array dipanggil pada non-Array (mis. Via Array.prototype.filter.call), jadi arraymungkin tidak benar-benar nyata Array: mungkin beberapa kelas lain yang ditentukan pengguna. (Sayangnya, itu tidak bisa diandalkan menjadi NodeList, yang mana ketika Anda benar-benar ingin melakukan itu, karena itu adalah objek host. Itu meninggalkan satu-satunya tempat Anda akan secara realistis melakukan itu sebagai argumentspseudo-Array.)
bobince
Penjelasan hebat dan contoh bagus! Sayangnya ini adalah aspek lain dari Javascript. Saya hanya tidak mengerti apa yang sangat mengerikan tentang melempar kesalahan ketika Anda menerima jenis yang salah. Dimungkinkan untuk memungkinkan pengetikan dinamis tanpa mengizinkan setiap kesalahan tak disengaja untuk membuat casting tipe. :(
Mike Williamson
"Menggunakan >>> 0 memastikan Anda memiliki bilangan bulat antara 0 dan 0xFFFFFFFF." akan seperti apa ifpernyataan ini ketika mencoba mengidentifikasi bahwa sisi kiri evaluasi bukan int? 'lemons'>>>0 === 0 && 0 >>>0 === 0mengevaluasi sebagai benar? meskipun lemon jelas sebuah kata ..?
Zze
58

Operator pergeseran kanan yang tidak ditandatangani digunakan dalam implementasi metode semua array ekstra Mozilla, untuk memastikan bahwa lengthproperti adalah bilangan bulat 32-bit yang tidak ditandatangani .

The lengthproperti objek array dijelaskan dalam spesifikasi sebagai:

Setiap objek Array memiliki properti panjang yang nilainya selalu bilangan bulat tidak negatif kurang dari 2 32 .

Operator ini adalah cara terpendek untuk mencapainya, metode array internal menggunakan ToUint32operasi, tetapi metode itu tidak dapat diakses dan ada pada spesifikasi untuk tujuan implementasi.

Implementasi ekstra array Mozilla mencoba untuk memenuhi ECMAScript 5 , lihat deskripsi Array.prototype.indexOfmetode (§ 15.4.4.14):

1. Biarkan O menjadi hasil dari panggilan ToObject melewati nilai ini 
   sebagai argumen.
2. Biarkan lenValue menjadi hasil dari memanggil metode internal [[Get]] dengan O 
   argumen "panjang".
3. Biarkan len menjadi ToUint32 (lenValue) .
....

Seperti yang Anda lihat, mereka hanya ingin mereproduksi perilaku ToUint32metode untuk mematuhi spesifikasi ES5 pada implementasi ES3, dan seperti yang saya katakan sebelumnya, operator shift kanan yang tidak bertanda tangan adalah cara termudah.

CMS
sumber
Sementara implementasi tambahan array yang ditautkan mungkin benar (atau hampir benar) kode masih merupakan contoh kode yang buruk. Mungkin bahkan komentar untuk memperjelas niat akan menyelesaikan situasi ini.
fmark
2
Apakah mungkin bahwa panjang array bukan bilangan bulat? Aku tidak bisa membayangkan itu, jadi ToUint32sepertinya ini tidak perlu bagiku.
Marcel Korpel
7
@ Marscel: Perlu diingat bahwa sebagian besar Array.prototypemetode sengaja generik , mereka dapat digunakan pada objek seperti array misalnya Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. The argumentsobjek juga merupakan contoh yang baik. Untuk objek array murni , tidak mungkin untuk mengubah jenis lengthproperti, karena mereka menerapkan metode internal [[Put ]] khusus, dan ketika tugas dibuat ke lengthproperti, sekali lagi dikonversi ToUint32dan tindakan lain diambil, seperti menghapus indeks di atas panjang baru ...
CMS
32

Itu adalah operator bit shift kanan yang tidak ditandatangani . Perbedaan antara ini dan operator bit shift kanan yang ditandatangani , adalah bahwa operator shift bit kanan yang tidak ditandatangani ( >>> ) mengisi dengan nol dari kiri, dan operator bit shift kanan yang ditandatangani ( >> ) mengisi dengan bit tanda, dengan demikian mempertahankan tanda nilai numerik saat digeser.

driis
sumber
Ivan, itu akan menggesernya dengan 0 tempat; pernyataan itu tidak akan mengubah apa pun.
Dean J
3
@Van, biasanya, saya akan mengatakan bahwa mengubah nilai dengan nol tempat sama sekali tidak masuk akal. Tapi ini Javascript, jadi mungkin ada makna di baliknya. Saya bukan guru Javascript, tetapi mungkin cara untuk memastikan bahwa nilai tersebut sebenarnya adalah bilangan bulat dalam bahasa Javasacript yang tidak diketik.
driis
2
@Van, lihat jawaban Justin di bawah ini. Ini sebenarnya cara untuk memastikan bahwa variabel len berisi angka.
driis
1
Selanjutnya, >>>dikonversi ke integer, yang +tidak dilakukan unary .
Rekursif
this.length >>> 0 mengonversi bilangan bulat yang ditandatangani menjadi yang tidak ditandatangani. Secara pribadi saya menemukan ini berguna ketika memuat file biner dengan int unsigned di dalamnya.
Matt Parkins
29

Driis telah cukup menjelaskan apa itu operator dan apa fungsinya. Inilah makna di baliknya / mengapa itu digunakan:

Menggeser ke mana pun dengan 0cara mengembalikan nomor asli dan akan dilemparkan nullke 0. Tampaknya kode contoh yang Anda lihat gunakan this.length >>> 0untuk memastikan bahwa lenitu numerik meskipun this.lengthtidak ditentukan.

Bagi banyak orang, operasi bitwise tidak jelas (dan Douglas Crockford / jslint menyarankan agar tidak menggunakan hal-hal seperti itu). Itu tidak berarti bahwa itu salah untuk dilakukan, tetapi metode yang lebih menguntungkan dan akrab ada untuk membuat kode lebih mudah dibaca. Cara yang lebih jelas untuk memastikan bahwa lenadalah 0merupakan salah satu dari dua metode berikut.

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

atau

// 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
sumber
1
Meskipun, solusi kedua Anda kadang-kadang akan mengevaluasi NaN.. Eg +{}... Ini mungkin yang terbaik untuk menggabungkan dua:+length||0
James
1
this.length adalah dalam konteks objek array, yang tidak bisa apa-apa selain bilangan bulat non-negatif (setidaknya dalam FF), jadi tidak mungkin di sini. Juga, {} || 1 mengembalikan {} jadi Anda tidak lebih baik jika this.length adalah objek. Keuntungan juga unary casting this.length dalam metode pertama adalah bahwa ia menangani kasus-kasus di mana this.length adalah NaN. Respons yang diedit untuk mencerminkan hal itu.
Justin Johnson
jslint akan mengeluh tentang var len = + this.length juga sebagai "plusses membingungkan". Douglas, kamu sangat pemilih!
Bayard Randel
Douglas pilih-pilih. Dan sementara argumennya bijak dan biasanya beralasan, apa yang dia katakan tidak absolut atau Injil.
Justin Johnson
15

>>>adalah operator shift kanan yang tidak ditandatangani ( lihat hal. 76 dari spesifikasi JavaScript 1.5 ), yang bertentangan dengan >>, yang ditandatangani Operator shift kanan.

>>> mengubah hasil pergeseran angka negatif karena itu tidak mempertahankan bit tanda ketika menggeser . Konsekuensi dari ini dapat dipahami dengan contoh, dari seorang penafsir:

$ 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

Jadi apa yang mungkin dimaksudkan untuk dilakukan di sini adalah untuk mendapatkan panjang, atau 0 jika panjangnya tidak ditentukan atau bukan bilangan bulat, seperti "cabbage"contoh di atas. Saya pikir dalam kasus ini aman untuk berasumsi bahwa hal this.lengthitu tidak akan pernah terjadi < 0. Namun demikian, saya berpendapat bahwa contoh ini adalah hack jahat , karena dua alasan:

  1. Perilaku <<<ketika menggunakan angka negatif, efek samping mungkin tidak dimaksudkan (atau kemungkinan terjadi) pada contoh di atas.

  2. Maksud kode tidak jelas , karena keberadaan pertanyaan ini diverifikasi.

Praktik terbaik mungkin menggunakan sesuatu yang lebih mudah dibaca kecuali kinerja sangat penting:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
sumber
Sooo ... @johncatfish benar? Ini untuk memastikan ini. Panjangnya tidak negatif?
Anthony
4
Mungkinkah kasus -1 >>> 0pernah terjadi dan jika demikian, apakah benar-benar diinginkan untuk mengubahnya ke 4294967295? Sepertinya ini akan menyebabkan loop berjalan beberapa kali lebih banyak dari yang diperlukan.
deceze
@menerima: Tanpa melihat implementasi this.lengthtidak mungkin untuk tahu. Untuk setiap implementasi "waras" panjang string tidak boleh negatif, tetapi kemudian orang mungkin berpendapat bahwa dalam lingkungan "waras" kita dapat mengasumsikan keberadaan this.lengthproperti yang selalu mengembalikan bilangan integral.
fmark
Anda mengatakan >>> tidak mempertahankan bit tanda .. ok .. Jadi, saya harus bertanya, ketika kita berurusan dengan angka negatif .. sebelum konversi >>> atau >>, apakah mereka dalam 2s compliement formulir, atau mereka dalam bentuk bilangan bulat yang ditandatangani, dan bagaimana kita tahu? Omong-omong, komplemen 2s saya pikir mungkin tidak dikatakan memiliki sedikit tanda .. ini adalah alternatif untuk notasi yang ditandatangani, tetapi dimungkinkan untuk menentukan tanda bilangan bulat
barlop
10

Dua alasan:

  1. Hasil >>> adalah "integral"

  2. undefined >>> 0 = 0 (karena JS akan mencoba dan memaksa LFS ke konteks numerik, ini akan bekerja untuk "foo" >>> 0, dll. juga)

Ingat bahwa angka dalam JS memiliki representasi internal ganda. Ini hanya cara "cepat" input kewarasan dasar panjang.

Namun , -1 >>> 0 (oops, kemungkinan bukan panjang yang diinginkan!)


sumber
0

Contoh Kode Java di bawah ini menjelaskan dengan baik:

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

Outputnya adalah sebagai berikut:

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