Toán tử >>> >>> là gì và bạn sử dụng nó như thế nào?


150

Tôi đã xem mã từ Mozilla có thêm phương thức lọc vào Array và nó có một dòng mã làm tôi bối rối.

var len = this.length >>> 0;

Tôi chưa bao giờ thấy >>> được sử dụng trong JavaScript trước đây.
Nó là gì và nó làm gì?


@CMS Đúng, mã / câu hỏi này đến từ những người đó; tuy nhiên, phản hồi ở đây cụ thể và có giá trị hơn so với trước đây.
Justin Johnson

2
Hoặc đó là một lỗi hoặc các chàng trai Mozilla đang cho rằng điều này có thể là -1. >>> là toán tử dịch chuyển không dấu nên var len sẽ luôn là 0 hoặc lớn hơn.
dùng347594

1
Ash Searle đã tìm thấy cách sử dụng nó - lật ngược sự thực thi của chúa tể JS (Doug Crockford) thành Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (mặc dù ông đã thực hiện các bài kiểm tra, haha).
Dan Beam

Câu trả lời:


211

Nó không chỉ chuyển đổi số không thành số, nó chuyển đổi chúng thành số có thể được biểu thị dưới dạng số nguyên không dấu 32 bit.

Mặc dù số JavaScript là nổi đúp chính xác (*), các nhà khai thác Bitwise ( <<, >>, &, |~) được định nghĩa về hoạt động trên nguyên 32-bit. Thực hiện thao tác bitwise chuyển đổi số thành int có chữ ký 32 bit, mất bất kỳ phân số và bit nào ở vị trí cao hơn 32, trước khi thực hiện phép tính và sau đó chuyển đổi trở lại thành Số.

Vì vậy, thực hiện thao tác bitwise không có hiệu ứng thực tế, như dịch chuyển sang phải 0 bit >>0, là cách nhanh chóng để làm tròn một số và đảm bảo nó nằm trong phạm vi int 32 bit. Ngoài ra, >>>toán tử ba , sau khi thực hiện thao tác không dấu, chuyển đổi kết quả tính toán của nó thành Số thành số nguyên không dấu thay vì số nguyên đã ký mà các số khác thực hiện, do đó, nó có thể được sử dụng để chuyển đổi phủ định sang bổ sung 32 bit-hai phiên bản như một số lượng lớn. Việc sử dụng >>>0đảm bảo bạn có số nguyên từ 0 đến 0xFFFFFFFF.

Trong trường hợp này, điều này rất hữu ích vì ECMAScript xác định các chỉ mục Mảng theo các số nguyên không dấu 32 bit. Vì vậy, nếu bạn đang cố gắng thực hiện array.filtertheo cách sao chép chính xác những gì tiêu chuẩn ECMAScript Fifth Edition nói, bạn sẽ chuyển số thành số không dấu 32 bit như thế này.

(Trên thực tế có rất ít nhu cầu thiết thực cho điều này là hy vọng mọi người sẽ không được thiết lập array.lengthđể 0.5, -1, 1e21hoặc 'LEMONS'. Nhưng đây là tác giả Javascript chúng ta đang nói về, vì vậy bạn không bao giờ biết ...)

Tóm lược:

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

(*: tốt, chúng được định nghĩa là hoạt động giống như phao. Tôi sẽ không ngạc nhiên nếu một số công cụ JavaScript thực sự sử dụng ints khi có thể, vì lý do hiệu suất. Nhưng đó sẽ là một chi tiết triển khai mà bạn sẽ không nhận được lợi thế của.)


2
+2 trong bảng mô tả và bảng độ sâu, -1 vì mảng.length tự xác thực và không thể được đặt tùy ý thành bất cứ thứ gì không phải là số nguyên hoặc 0 (FF ném lỗi này RangeError: invalid array length:).
Justin Johnson

4
Tuy nhiên, thông số kỹ thuật cố tình cho phép nhiều hàm Array được gọi trên non-Array (ví dụ: thông qua Array.prototype.filter.call), do đó arraycó thể không thực sự là một thực tế Array: nó có thể là một lớp do người dùng định nghĩa khác. (Thật không may, nó có thể không đáng tin cậy là một NodeList, đó là khi bạn muốn thực sự muốn làm điều đó, vì đó là một đối tượng chủ lá Đó là chỉ đặt bạn thực tế muốn làm điều đó như. argumentsPseudo-Array.)
bobince

Giải thích tuyệt vời và ví dụ tuyệt vời! Thật không may, đây là một khía cạnh điên rồ khác của Javascript. Tôi chỉ không hiểu điều gì kinh khủng khi ném lỗi khi bạn nhận được loại sai. Có thể cho phép nhập động mà không cho phép mọi lỗi vô ý tạo ra kiểu truyền. :(
Mike Williamson

"Sử dụng >>> 0 đảm bảo bạn có số nguyên trong khoảng từ 0 đến 0xFFFFFFFF." Điều gì sẽ ifnhìn tuyên bố như thế này khi cố gắng để xác định rằng phía bên trái của việc đánh giá không phải là một int? 'lemons'>>>0 === 0 && 0 >>>0 === 0đánh giá là đúng? mặc dù chanh rõ ràng là một từ ..?
Zze

58

Toán tử dịch chuyển phải không dấu được sử dụng trong tất cả các triển khai phương thức của mảng phụ của mảng , để đảm bảo rằng thuộc lengthtính là số nguyên 32 bit không dấu .

Các lengthtài sản của đối tượng mảng được mô tả trong đặc tả như sau:

Mỗi đối tượng Array có thuộc tính độ dài có giá trị luôn là số nguyên không âm nhỏ hơn 2 32 .

Toán tử này là cách ngắn nhất để đạt được nó, các phương thức mảng bên trong sử dụng ToUint32thao tác, nhưng phương thức đó không thể truy cập và tồn tại trên đặc tả cho các mục đích thực hiện.

Việc triển khai bổ sung mảng Mozilla cố gắng tuân thủ ECMAScript 5 , xem mô tả của Array.prototype.indexOfphương thức (§ 15.4.4.14):

1. Gọi O là kết quả của việc gọi ToObject truyền giá trị này 
   như là đối số.
2. Đặt lenValue là kết quả của việc gọi phương thức nội bộ [[Get]] của O với 
   đối số "chiều dài".
3. Đặt len ​​là ToUint32 (lenValue) .
....

Như bạn có thể thấy, họ chỉ muốn tái tạo hành vi của ToUint32phương thức để tuân thủ thông số ES5 khi triển khai ES3 và như tôi đã nói trước đây, toán tử dịch chuyển phải không dấu là cách dễ nhất.


Mặc dù việc thực hiện bổ sung mảng được liên kết có thể đúng (hoặc gần đúng), mã vẫn là một ví dụ mã xấu. Có lẽ ngay cả một bình luận để làm rõ ý định sẽ giải quyết tình huống này.
đánh dấu

2
Có thể là chiều dài của một mảng không phải là một số nguyên? Tôi không thể tưởng tượng điều đó, vì vậy loại này ToUint32có vẻ hơi không cần thiết đối với tôi.
Marcel Korpel

7
@Marcel: Hãy nhớ rằng hầu hết các Array.prototypephương thức đều có chủ đích chung , chúng có thể được sử dụng trên các đối tượng giống như mảng, ví dụ Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. Đối argumentstượng cũng là một ví dụ tốt. Đối với các đối tượng mảng thuần túy , không thể thay đổi loại thuộc lengthtính, vì chúng thực hiện một phương thức bên trong [[Put ]] đặc biệt và khi chuyển nhượng được thực hiện cho thuộc lengthtính, một lần nữa được chuyển đổi ToUint32và các hành động khác được thực hiện, như xóa các chỉ mục ở trên độ dài mới ...
CMS

32

Đó là toán tử dịch chuyển bit phải không dấu . Sự khác biệt giữa toán tử dịch chuyển bit phải đã ký , là toán tử dịch chuyển bit phải không dấu ( >>> ) điền vào các số 0 từ bên trái và toán tử dịch chuyển bit phải đã ký ( >> ) điền vào bit dấu, do đó bảo toàn dấu của giá trị số khi dịch chuyển.


Ivan, điều đó sẽ thay đổi nó 0 điểm; tuyên bố đó sẽ không thay đổi bất cứ điều gì.
Trưởng khoa J

3
@Ivan, thông thường, tôi sẽ nói rằng việc thay đổi một giá trị bằng 0 vị trí hoàn toàn không có ý nghĩa. Nhưng đây là Javascript, vì vậy có thể có một ý nghĩa đằng sau nó. Tôi không phải là một bậc thầy về Javascript, nhưng nó có thể là một cách để đảm bảo rằng giá trị trên thực tế là một số nguyên trong ngôn ngữ Javasacript không chữ.
Driis

2
@Ivan, xem câu trả lời của Justin bên dưới. Thực tế đây là một cách để đảm bảo rằng biến len có chứa một số.
Driis

1
Hơn nữa, >>>chuyển đổi thành một số nguyên, mà unary +không làm.
đệ quy

this.length >>> 0 chuyển đổi một số nguyên đã ký thành một số nguyên không dấu. Cá nhân tôi đã thấy điều này hữu ích khi tải một tệp nhị phân có số nguyên không dấu trong đó.
Matt Parkins

29

Driis đã giải thích đầy đủ những gì người vận hành và những gì nó làm. Đây là ý nghĩa đằng sau nó / tại sao nó được sử dụng:

Chuyển bất kỳ hướng nào bằng cách 0trả về số ban đầu và sẽ nullchuyển sang0 . Có vẻ như mã ví dụ mà bạn đang xem đang sử dụng this.length >>> 0để đảm bảo đó lenlà số ngay cả khi this.lengthkhông được xác định.

Đối với nhiều người, các hoạt động bitwise không rõ ràng (và Douglas Crockford / jslint đề nghị không sử dụng những thứ như vậy). Điều đó không có nghĩa là nó sai, nhưng các phương thức quen thuộc và thuận lợi hơn tồn tại để làm cho mã dễ đọc hơn. Một cách rõ ràng hơn để đảm bảo rằnglen0là một trong hai phương pháp sau đây.

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

hoặc là

// Cast this.length to a number, or use 0 if this.length is
// NaN/undefined (evaluates to false)
var len = +this.length || 0; 

1
Mặc dù, giải pháp thứ hai của bạn đôi khi sẽ đánh giá thành NaN.. Ví dụ +{}... Có lẽ tốt nhất là kết hợp cả hai:+length||0
James

1
this.length nằm trong ngữ cảnh của đối tượng mảng, không thể là bất cứ thứ gì ngoài một số nguyên không âm (ít nhất là trong FF), vì vậy nó không phải là một khả năng ở đây. Ngoài ra, {} || 1 trả về {} vì vậy bạn sẽ không khá hơn nếu this.length là một đối tượng. Lợi ích của việc unary unary cast this.length trong phương thức đầu tiên là nó xử lý các trường hợp trong đó this.length là NaN. Chỉnh sửa phản ứng để phản ánh điều đó.
Justin Johnson

jslint sẽ phàn nàn về var len = + this.length cũng là "kìm gây nhầm lẫn". Douglas, bạn thật kén chọn!
Bayard Randel

Douglas rất kén chọn. Và trong khi những lập luận của ông là khôn ngoan và thường có cơ sở, những gì ông nói không phải là tuyệt đối cũng không phải là phúc âm.
Justin Johnson

15

>>>là toán tử dịch chuyển phải không dấu ( xem trang 76 của đặc tả JavaScript 1.5 ), trái ngược với >>, ký hiệu đã ký điều hành sự thay đổi ngay.

>>>thay đổi kết quả của việc dịch chuyển số âm vì nó không bảo toàn bit dấu khi dịch chuyển . Hậu quả của điều này có thể được hiểu bằng ví dụ, từ một phiên dịch viên:

$ 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

Vì vậy, những gì có thể được dự định được thực hiện ở đây là để có được độ dài, hoặc 0 nếu độ dài không xác định hoặc không phải là một số nguyên, theo "cabbage"ví dụ trên. Tôi nghĩ trong trường hợp này là an toàn để cho rằng this.lengthsẽ không bao giờ < 0. Tuy nhiên, tôi cho rằng ví dụ này là một hack khó chịu , vì hai lý do:

  1. Hành vi <<<khi sử dụng số âm, tác dụng phụ có thể không có ý định (hoặc có khả năng xảy ra) trong ví dụ trên.

  2. Ý định của mã là không rõ ràng , vì sự tồn tại của câu hỏi này xác minh.

Thực hành tốt nhất có lẽ là sử dụng một cái gì đó dễ đọc hơn trừ khi hiệu suất là cực kỳ quan trọng:

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

Sooo ... @johncatfish có đúng không? Đó là để đảm bảo điều này.length là không âm?
Anthony

4
Có thể trường hợp -1 >>> 0đã từng xảy ra và nếu vậy, nó có thực sự mong muốn chuyển nó sang 4294967295 không? Có vẻ như điều này sẽ khiến vòng lặp chạy nhiều lần hơn mức cần thiết.
lừa dối

@deceze: Nếu không thấy việc thực hiện this.lengththì không thể biết được. Đối với bất kỳ triển khai "lành mạnh" nào, độ dài của chuỗi không bao giờ âm, nhưng sau đó người ta có thể lập luận rằng trong môi trường "lành mạnh", chúng ta có thể giả sử sự tồn tại của một thuộc this.lengthtính luôn trả về một số nguyên.
đánh dấu

bạn nói >>> không bảo toàn bit dấu .. ok .. Vì vậy, tôi phải hỏi, khi chúng tôi xử lý các số âm .. trước bất kỳ >>> hoặc >> chuyển đổi nào, chúng có trong 2 giây không hình thức, hoặc chúng ở dạng số nguyên đã ký, và làm thế nào để chúng ta biết? Nhân tiện, phần bổ sung 2 giây tôi nghĩ có lẽ không được nói là có một dấu hiệu .. đó là một thay thế cho ký hiệu đã ký, nhưng có thể xác định dấu của một số nguyên
barlop

10

Hai lý do:

  1. Kết quả của >>> là một "tích phân"

  2. không xác định >>> 0 = 0 (vì JS sẽ thử và ép LFS vào ngữ cảnh số, điều này cũng sẽ hoạt động cho "foo" >>> 0, v.v.)

Hãy nhớ rằng các số trong JS có biểu diễn bên trong là gấp đôi. Đó chỉ là một cách "nhanh chóng" của sự tỉnh táo đầu vào cơ bản cho chiều dài.

Tuy nhiên , -1 >>> 0 (rất tiếc, có thể không phải là độ dài mong muốn!)


0

Mã Java mẫu dưới đây giải thích tốt:

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

Đầu ra là như sau:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.