Tại sao 2 == [2] trong JavaScript?


164

Gần đây tôi đã phát hiện ra rằng 2 == [2]trong JavaScript. Hóa ra, trò hề này có một vài hậu quả thú vị:

var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Tương tự, các công việc sau:

var a = { "abc" : 1 };
a[["abc"]] === a["abc"]; // this is also true

Ngay cả người lạ vẫn còn, điều này cũng hoạt động:

[[[[[[[2]]]]]]] == 2; // this is true too! WTF?

Những hành vi này có vẻ nhất quán trên tất cả các trình duyệt.

Bất cứ ý tưởng tại sao đây là một tính năng ngôn ngữ?

Dưới đây là những hậu quả điên rồ hơn của "tính năng" này:

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

var a = [0];
a == a // true
a == !a // also true, WTF?

Những ví dụ này được tìm thấy bởi jimbojw http://jimbojw.com danh tiếng cũng như walkeyerobot .

Câu trả lời:


134

Bạn có thể tra cứu thuật toán so sánh trong ECMA-spec (các phần có liên quan của ECMA-262, phiên bản thứ 3 cho vấn đề của bạn: 11.9.3, 9.1, 8.6.2.6).

Nếu bạn dịch các thuật toán trừu tượng liên quan trở lại thành JS, điều gì xảy ra khi đánh giá 2 == [2]về cơ bản là:

2 === Number([2].valueOf().toString())

trong đó valueOf()các mảng trả về chính mảng đó và biểu diễn chuỗi của mảng một phần tử là biểu diễn chuỗi của phần tử đơn.

Điều này cũng giải thích ví dụ thứ ba [[[[[[[2]]]]]]].toString()vẫn chỉ là chuỗi 2.

Như bạn có thể thấy, có khá nhiều phép thuật đằng sau hậu trường, đó là lý do tại sao tôi thường chỉ sử dụng toán tử bình đẳng nghiêm ngặt ===.

Ví dụ thứ nhất và thứ hai dễ theo dõi hơn vì tên thuộc tính luôn là chuỗi, vì vậy

a[[2]]

tương đương với

a[[2].toString()]

đó chỉ là

a["2"]

Hãy nhớ rằng ngay cả các khóa số cũng được coi là tên thuộc tính (tức là chuỗi) trước khi bất kỳ phép thuật mảng nào xảy ra.


10

Đó là do chuyển đổi kiểu ngầm định của ==toán tử.

[2] được chuyển đổi thành Số là 2 khi so sánh với Số. Hãy thử toán tử đơn nguyên +trên [2].

> +[2]
2

Những người khác đang nói [2] được chuyển đổi thành một chuỗi. +"2"cũng là số 2.
dlamblin

1
Thật ra, nó không dễ như vậy. [2] được chuyển đổi thành chuỗi sẽ gần hơn nhưng hãy xem ecma
neo

10
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Ở bên phải của phương trình, chúng ta có [2], trả về một kiểu số có giá trị 2. Ở bên trái, trước tiên chúng ta tạo một mảng mới với một đối tượng là 2. Sau đó, chúng ta gọi một [( mảng ở đây)]. Tôi không chắc chắn nếu điều này đánh giá một chuỗi hoặc một số. 2 hoặc "2". Hãy lấy trường hợp chuỗi đầu tiên. Tôi tin rằng ["2"] sẽ tạo một biến mới và trả về null. null! == 2. Vì vậy, giả sử nó thực sự đang chuyển đổi thành một số. a [2] sẽ trả về 2. 2 và 2 khớp loại (vì vậy === hoạt động) và giá trị. Tôi nghĩ rằng nó đang ngầm chuyển đổi mảng thành một số vì [giá trị] mong đợi một chuỗi hoặc số. Có vẻ như số lượng được ưu tiên cao hơn.

Bên cạnh đó, tôi tự hỏi ai là người xác định quyền ưu tiên đó. Có phải vì [2] có một số là mục đầu tiên, nên nó chuyển thành số? Hoặc là khi truyền một mảng vào [mảng], nó cố gắng biến mảng thành một số trước, sau đó là chuỗi. Ai biết?

var a = { "abc" : 1 };
a[["abc"]] === a["abc"];

Trong ví dụ này, bạn đang tạo một đối tượng được gọi là a với một thành viên được gọi là abc. Phía bên phải của phương trình là khá đơn giản; nó tương đương với a.abc. Điều này trả về 1. Phía bên trái trước tiên tạo ra một mảng bằng chữ ["abc"]. Sau đó, bạn tìm kiếm một biến trên một đối tượng bằng cách chuyển vào mảng vừa tạo. Vì điều này mong đợi một chuỗi, nó chuyển đổi mảng thành một chuỗi. Điều này bây giờ ước tính cho một ["abc"], bằng 1. 1 và 1 là cùng loại (đó là lý do tại sao === hoạt động) và giá trị bằng nhau.

[[[[[[[2]]]]]]] == 2; 

Đây chỉ là một chuyển đổi ngầm. === sẽ không hoạt động trong tình huống này vì có loại không khớp.


Câu trả lời cho câu hỏi của bạn về mức độ ưu tiên: ==áp dụng ToPrimitive()cho mảng, lần lượt gọi toString()phương thức của nó , vì vậy cái mà bạn thực sự so sánh là số 2với chuỗi "2"; so sánh giữa một chuỗi và một số được thực hiện bằng cách chuyển đổi chuỗi
Christoph

8

Đối với ==trường hợp, đây là lý do Doug Crockford khuyên bạn nên luôn luôn sử dụng ===. Nó không thực hiện bất kỳ chuyển đổi loại ngầm định.

Đối với các ví dụ với ===, chuyển đổi kiểu ẩn được thực hiện trước khi toán tử đẳng thức được gọi.


7
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

Điều đó thật thú vị, thực ra không phải [0] là đúng và sai, thực ra

[0] == true // false

Đó là cách xử lý hài hước của javascript nếu toán tử if ().


4
Thật ra, đó là cách ==làm việc vui nhộn ; nếu bạn sử dụng một diễn viên rõ ràng thực sự (tức là Boolean([0])hoặc !![0]), bạn sẽ thấy rằng nó [0]sẽ đánh giá truetrong bối cảnh boolean như nó nên: trong JS, bất kỳ đối tượng nào cũng được xem xéttrue
Christoph

6

Một mảng của một mục có thể được coi là chính mục đó.

Điều này là do gõ vịt. Vì "2" == 2 == [2] và có thể hơn thế nữa.


4
bởi vì chúng không khớp với nhau trong ví dụ đầu tiên, phía bên trái được đánh giá đầu tiên và chúng kết hợp với nhau theo kiểu.
Shawn

8
Ngoài ra, tôi không nghĩ gõ vịt là từ chính xác ở đây. Nó là nhiều hơn để làm với chuyển đổi loại ngầm định được thực hiện bởi ==nhà điều hành trước khi so sánh.
Chetan S

14
điều này không liên quan gì đến việc gõ vịt mà thay vào đó là gõ yếu, tức là chuyển đổi kiểu ngầm định
Christoph

@Chetan: những gì anh ấy nói;)
Christoph

2
Những gì Chetan và Christoph nói.
Tim Down

3

Để thêm một chút chi tiết cho các câu trả lời khác ... khi so sánh Arrayvới một Number, Javascript sẽ chuyển đổi Arrayvới parseFloat(array). Bạn có thể tự thử nó trong bảng điều khiển (ví dụ: Fireorms hoặc Web Inspector) để xem những Arraygiá trị khác nhau được chuyển đổi thành.

parseFloat([2]); // 2
parseFloat([2, 3]); // 2
parseFloat(['', 2]); // NaN

Đối với Arrays, parseFloatthực hiện thao tác trên Arraythành viên đầu tiên và loại bỏ phần còn lại.

Chỉnh sửa: Theo chi tiết của Christoph, có thể là nó đang sử dụng mẫu dài hơn trong nội bộ, nhưng kết quả luôn giống hệt nhau parseFloat, vì vậy bạn luôn có thể sử dụng parseFloat(array)làm tốc ký để biết chắc chắn nó sẽ được chuyển đổi như thế nào.


2

Bạn đang so sánh 2 đối tượng trong mọi trường hợp .. Đừng sử dụng ==, nếu bạn đang nghĩ đến việc so sánh, bạn đang có === trong tâm trí và không ==. == thường có thể cho hiệu ứng điên rồ. Tìm kiếm những phần tốt trong ngôn ngữ :)


0

Giải thích cho phần EDIT của câu hỏi:

Ví dụ 1

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

Kiểu chữ đầu tiên [0] thành giá trị nguyên thủy theo câu trả lời của Christoph ở trên, chúng tôi có "0" ( [0].valueOf().toString())

"0" == false

Bây giờ, typecast Boolean (false) thành Number và sau đó String ("0") thành Number

Number("0") == Number(false)
or  0 == 0 
so, [0] == false  // true

Đối với iftuyên bố, nếu không có một sự so sánh rõ ràng trong điều kiện if bản thân, đánh giá lại tình trạng cho truthy giá trị.

Chỉ có 6 giá trị giả: false, null, không xác định, 0, NaN và chuỗi rỗng "". Và bất cứ điều gì không phải là một giá trị giả là một giá trị trung thực.

Vì [0] không phải là giá trị giả, nó là giá trị trung thực, ifcâu lệnh đánh giá là đúng & thực thi câu lệnh.


Ví dụ thứ 2

var a = [0];
a == a // true
a == !a // also true, WTF?

Một lần nữa gõ các giá trị thành nguyên thủy,

    a = a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == "0" // true; same type, same value


a == !a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == !"0"
or  "0" == false
or  Number("0") == Number(false)
or  0 = 0   // true
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.