Điều này hợp lệ và trả về chuỗi "10"
trong JavaScript ( thêm ví dụ ở đây ):
console.log(++[[]][+[]]+[+[]])
Tại sao? Có chuyện gì đang xảy ra ở đây?
Điều này hợp lệ và trả về chuỗi "10"
trong JavaScript ( thêm ví dụ ở đây ):
console.log(++[[]][+[]]+[+[]])
Tại sao? Có chuyện gì đang xảy ra ở đây?
Câu trả lời:
Nếu chúng ta chia nó ra, mớ hỗn độn bằng:
++[[]][+[]]
+
[+[]]
Trong JavaScript, đúng là như vậy +[] === 0
. +
chuyển đổi một cái gì đó thành một số và trong trường hợp này nó sẽ chuyển sang +""
hoặc 0
(xem chi tiết thông số kỹ thuật bên dưới).
Do đó, chúng ta có thể đơn giản hóa nó ( ++
có quyền ưu tiên hơn +
):
++[[]][0]
+
[0]
Bởi vì [[]][0]
có nghĩa là: lấy phần tử đầu tiên từ [[]]
, đúng là:
[[]][0]
trả về mảng bên trong ( []
). Do tham chiếu nên nói sai [[]][0] === []
, nhưng hãy gọi mảng bên trong A
để tránh ký hiệu sai.
++
trước toán hạng của nó có nghĩa là tăng thêm một lần và trả về kết quả tăng dần. Như vậy ++[[]][0]
là tương đương với Number(A) + 1
(hoặc +A + 1
).
Một lần nữa, chúng ta có thể đơn giản hóa mớ hỗn độn thành thứ gì đó dễ đọc hơn. Hãy thay thế []
lại cho A
:
(+[] + 1)
+
[0]
Trước khi +[]
có thể ép mảng thành số 0
, nó cần phải được buộc thành một chuỗi trước ""
, một lần nữa. Cuối cùng, 1
được thêm vào, kết quả trong 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Hãy đơn giản hóa nó hơn nữa:
1
+
[0]
Ngoài ra, điều này đúng trong JavaScript : [0] == "0"
, bởi vì nó tham gia một mảng với một phần tử. Tham gia sẽ nối các yếu tố cách nhau bởi ,
. Với một yếu tố, bạn có thể suy luận rằng logic này sẽ dẫn đến chính yếu tố đầu tiên.
Trong trường hợp này, +
thấy hai toán hạng: một số và một mảng. Bây giờ nó đang cố gắng ép buộc hai người thành cùng một loại. Đầu tiên, mảng được ép thành chuỗi "0"
, tiếp theo, số được ép thành chuỗi ( "1"
). Chuỗi số Chuỗi+
===
.
"1" + "0" === "10" // Yay!
Chi tiết thông số kỹ thuật cho +[]
:
Đây là một mê cung, nhưng để làm +[]
, đầu tiên, nó đang được chuyển đổi thành một chuỗi bởi vì đó là những gì +
nói:
11.4.6 Unary + Toán tử
Toán tử unary + chuyển đổi toán hạng của nó thành loại Số.
UnaryExpression sản xuất: + UnaryExpression được đánh giá như sau:
Đặt expr là kết quả của việc đánh giá UnaryExpression.
Trả về ToNumber (GetValue (expr)).
ToNumber()
nói:
Vật
Áp dụng các bước sau:
Đặt primValue là ToPrimitive (đối số đầu vào, chuỗi gợi ý).
Quay trở lại ToString (primValue).
ToPrimitive()
nói:
Vật
Trả về một giá trị mặc định cho Object. Giá trị mặc định của một đối tượng được lấy bằng cách gọi phương thức bên trong [[DefaultValue]] của đối tượng, chuyển qua gợi ý tùy chọn PreferredType. Hành vi của phương thức bên trong [[DefaultValue]] được xác định bởi đặc tả này cho tất cả các đối tượng ECMAScript gốc trong 8.12.8.
[[DefaultValue]]
nói:
8.12.8 [[DefaultValue]] (gợi ý)
Khi phương thức bên trong [[DefaultValue]] của O được gọi với Chuỗi gợi ý, các bước sau được thực hiện:
Đặt toString là kết quả của việc gọi phương thức bên trong [[Get]] của đối tượng O với đối số "toString".
Nếu IsCallable (toString) là đúng thì,
a. Đặt str là kết quả của việc gọi phương thức nội bộ [[Gọi]] của toString, với O là giá trị này và danh sách đối số trống.
b. Nếu str là một giá trị nguyên thủy, trả về str.
Một .toString
mảng nói:
15.4.4.2 Array.prototype.toString ()
Khi phương thức toString được gọi, các bước sau được thực hiện:
Đặt mảng là kết quả của việc gọi ToObject trên giá trị này.
Đặt func là kết quả của việc gọi phương thức bên trong [[Get]] của mảng với đối số "tham gia".
Nếu IsCallable (func) là false, thì hãy để func là phương thức tích hợp chuẩn Object.prototype.toString (15.2.4.2).
Trả về kết quả của việc gọi phương thức nội bộ [[Gọi]] của func cung cấp mảng làm giá trị này và danh sách đối số trống.
Vì vậy, +[]
đi xuống +""
, bởi vì [].join() === ""
.
Một lần nữa, +
được định nghĩa là:
11.4.6 Unary + Toán tử
Toán tử unary + chuyển đổi toán hạng của nó thành loại Số.
UnaryExpression sản xuất: + UnaryExpression được đánh giá như sau:
Đặt expr là kết quả của việc đánh giá UnaryExpression.
Trả về ToNumber (GetValue (expr)).
ToNumber
được định nghĩa ""
là:
MV của StringNumericLiteral ::: [trống] là 0.
Vì vậy +"" === 0
, và do đó +[] === 0
.
true
nếu cả giá trị và loại đều giống nhau. 0 == ""
trả về true
(giống sau khi chuyển đổi loại), nhưng 0 === ""
là false
(không phải cùng loại).
1 + [0]
, không "1" + [0]
, bởi vì ++
toán tử tiền tố ( ) luôn trả về một số. Xem bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
trả về thực sự 1
, nhưng ++[]
ném một lỗi. Điều này là đáng chú ý bởi vì nó trông giống như ++[[]][0]
sôi lên ++[]
. Bạn có thể có bất kỳ ý tưởng tại sao ++[]
ném một lỗi trong khi ++[[]][0]
không?
PutValue
cuộc gọi (theo thuật ngữ ES3, 8.7.2) trong hoạt động tiền tố. PutValue
yêu cầu một Tài liệu tham khảo trong khi []
bản thân một biểu thức không tạo ra một Tài liệu tham khảo. Một biểu thức có chứa một tham chiếu biến (giả sử chúng ta đã xác định trước var a = []
đó ++a
hoạt động) hoặc truy cập thuộc tính của một đối tượng (chẳng hạn như [[]][0]
) tạo ra một Tham chiếu. Nói một cách đơn giản hơn, toán tử tiền tố không chỉ tạo ra một giá trị, nó còn cần một nơi nào đó để đặt giá trị đó.
var a = []; ++a
, a
là 1. Sau khi thực hiện ++[[]][0]
, mảng được tạo bởi [[]]
biểu thức bây giờ chỉ chứa số 1 tại chỉ mục 0. ++
yêu cầu Tham chiếu để thực hiện việc này.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Sau đó, chúng tôi có một chuỗi nối
1+[0].toString() = 10
===
chứ không phải =>
?
Sau đây được điều chỉnh từ một bài đăng trên blog trả lời câu hỏi này mà tôi đã đăng trong khi câu hỏi này vẫn còn đóng. Các liên kết đến (bản sao HTML của) thông số ECMAScript 3, vẫn là đường cơ sở cho JavaScript trong các trình duyệt web được sử dụng phổ biến hiện nay.
Đầu tiên, một nhận xét: loại biểu thức này sẽ không bao giờ xuất hiện trong bất kỳ môi trường sản xuất (lành mạnh) nào và chỉ được sử dụng như một bài tập trong việc người đọc biết rõ các cạnh bẩn của JavaScript như thế nào. Nguyên tắc chung mà các nhà khai thác JavaScript chuyển đổi ngầm giữa các loại là hữu ích, cũng như một số chuyển đổi phổ biến, nhưng phần lớn chi tiết trong trường hợp này thì không.
Biểu thức ++[[]][+[]]+[+[]]
ban đầu có thể trông khá hùng vĩ và tối nghĩa, nhưng thực sự tương đối dễ dàng chia thành các biểu thức riêng biệt. Dưới đây tôi chỉ đơn giản là thêm dấu ngoặc đơn cho rõ ràng; Tôi có thể đảm bảo với bạn rằng họ không thay đổi gì, nhưng nếu bạn muốn xác minh điều đó thì cứ thoải mái đọc về toán tử nhóm . Vì vậy, biểu thức có thể được viết rõ ràng hơn như
( ++[[]][+[]] ) + ( [+[]] )
Phá vỡ điều này, chúng ta có thể đơn giản hóa bằng cách quan sát +[]
đánh giá đó 0
. Để thỏa mãn bản thân tại sao điều này là đúng, hãy kiểm tra các nhà điều hành + unary và làm theo các đường mòn quanh co nhẹ mà kết thúc với ToPrimitive chuyển đổi mảng rỗng vào một chuỗi rỗng, sau đó được cuối cùng chuyển đổi sang 0
bằng ToNumber . Bây giờ chúng ta có thể thay thế 0
cho từng trường hợp +[]
:
( ++[[]][0] ) + [0]
Đơn giản hơn rồi. Đối với ++[[]][0]
, đó là sự kết hợp của toán tử gia tăng tiền tố ( ++
), một mảng bằng chữ xác định một mảng với một phần tử duy nhất là một mảng trống ( [[]]
) và một bộ truy cập thuộc tính ( [0]
) được gọi trên mảng được xác định bởi mảng bằng chữ.
Vì vậy, chúng ta có thể đơn giản hóa [[]][0]
để chỉ []
và chúng ta có ++[]
, phải không? Trong thực tế, đây không phải là trường hợp bởi vì đánh giá ++[]
ném một lỗi, ban đầu có thể có vẻ khó hiểu. Tuy nhiên, một chút suy nghĩ về bản chất của ++
việc làm rõ điều này: nó được sử dụng để tăng một biến (ví dụ ++i
) hoặc một thuộc tính đối tượng (ví dụ ++obj.count
). Nó không chỉ đánh giá một giá trị, nó còn lưu trữ giá trị đó ở đâu đó. Trong trường hợp ++[]
, nó không có nơi nào để đặt giá trị mới (bất kể nó có thể là gì) vì không có tham chiếu đến một thuộc tính hoặc biến để cập nhật. Trong các thuật ngữ cụ thể, điều này được bao phủ bởi hoạt động PutValue nội bộ , được gọi bởi toán tử gia tăng tiền tố.
Vậy sau đó, ++[[]][0]
làm gì? Vâng, theo logic tương tự như +[]
, mảng bên trong được chuyển đổi thành 0
và giá trị này được tăng lên 1
để cung cấp cho chúng ta một giá trị cuối cùng là 1
. Giá trị của thuộc tính 0
trong mảng bên ngoài được cập nhật 1
và toàn bộ biểu thức ước tính 1
.
Điều này để lại cho chúng tôi
1 + [0]
... đó là một cách sử dụng đơn giản của toán tử cộng . Cả hai toán hạng trước tiên được chuyển đổi thành nguyên hàm và nếu một trong hai giá trị nguyên thủy là một chuỗi, việc nối chuỗi được thực hiện, nếu không thì phép cộng số được thực hiện. [0]
chuyển đổi thành "0"
, do đó, nối chuỗi được sử dụng, sản xuất "10"
.
Là một thức sang một bên, một cái gì đó mà có thể không ngay lập tức rõ ràng là trọng hoặc một trong những toString()
hay valueOf()
phương pháp Array.prototype
sẽ làm thay đổi kết quả của biểu thức, bởi vì cả hai đều được kiểm tra và sử dụng nếu có khi chuyển đổi một đối tượng vào một giá trị nguyên thủy. Ví dụ như sau
Array.prototype.toString = function() {
return "foo";
};
++[[]][+[]]+[+[]]
... Sản xuất "NaNfoo"
. Tại sao điều này xảy ra lại là một bài tập cho người đọc ...
Hãy làm cho nó đơn giản:
++[[]][+[]]+[+[]] = "10"
var a = [[]][+[]];
var b = [+[]];
// so a == [] and b == [0]
++a;
// then a == 1 and b is still that array [0]
// when you sum the var a and an array, it will sum b as a string just like that:
1 + "0" = "10"
Cái này đánh giá giống nhau nhưng nhỏ hơn một chút
+!![]+''+(+[])
đánh giá là
+(true) + '' + (0)
1 + '' + 0
"10"
Vì vậy, bây giờ bạn đã có được điều đó, hãy thử cái này:
_=$=+[],++_+''+$
"10"
+ [] ước tính thành 0 [...] sau đó tính tổng (+ thao tác) với bất kỳ thứ gì chuyển đổi nội dung mảng thành biểu diễn chuỗi của nó bao gồm các phần tử được nối bằng dấu phẩy.
Bất cứ điều gì khác như lấy chỉ số của mảng (có mức độ ưu tiên hơn so với hoạt động +) là thứ tự và không có gì thú vị.
Có lẽ cách ngắn nhất có thể để đánh giá biểu thức thành "10" không có chữ số là:
+!+[] + [+[]]
// "10"
-~[] + [+[]]
// "10"
// ========== Giải thích ========== \\
+!+[]
: +[]
Chuyển đổi thành 0. !0
chuyển đổi thành true
. +true
chuyển đổi thành 1.
-~[]
= -(-1)
đó là 1
[+[]]
: +[]
Chuyển đổi thành 0. [0]
là một mảng có một phần tử 0 duy nhất.
Sau đó, JS đánh giá biểu thức 1 + [0]
, do đó Number + Array
. Sau đó, đặc tả ECMA hoạt động: +
toán tử chuyển đổi cả hai toán hạng thành một chuỗi bằng cách gọi các toString()/valueOf()
hàm từ Object
nguyên mẫu cơ sở . Nó hoạt động như một hàm cộng nếu cả hai toán hạng của một biểu thức chỉ là số. Thủ thuật là các mảng dễ dàng chuyển đổi các phần tử của chúng thành biểu diễn chuỗi được nối.
Vài ví dụ:
1 + {} // "1[object Object]"
1 + [] // "1"
1 + new Date() // "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"
Có một ngoại lệ tốt đẹp là hai Objects
kết quả bổ sung trong NaN
:
[] + [] // ""
[1] + [2] // "12"
{} + {} // NaN
{a:1} + {b:2} // NaN
[1, {}] + [2, {}] // "1,[object Object]2,[object Object]"
+ '' hoặc + [] ước tính 0.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
là không tương đương với ""
. Đầu tiên phần tử được trích xuất, sau đó được chuyển đổi bởi ++
.
Từng bước của bước đó, +
biến giá trị thành một số và nếu bạn thêm vào một mảng trống +[]
... vì nó trống và bằng 0
, nó sẽ
Vì vậy, từ đó, bây giờ hãy nhìn vào mã của bạn, đó là ++[[]][+[]]+[+[]]
...
Và có điểm cộng giữa chúng ++[[]][+[]]
+[+[]]
Vì vậy, những thứ này [+[]]
sẽ trở lại [0]
khi chúng có một mảng trống được chuyển đổi sang 0
bên trong mảng khác ...
Vì vậy, như tưởng tượng, giá trị đầu tiên là một mảng 2 chiều với một mảng bên trong ... vì vậy [[]][+[]]
sẽ bằng với [[]][0]
nó sẽ trả về []
...
Và cuối cùng, ++
chuyển đổi nó và tăng nó thành 1
...
Vì vậy, bạn có thể tưởng tượng, 1
+ "0"
sẽ "10"
...
+[]
đưa ra một mảng trống để0
... sau đó lãng phí một buổi chiều ...;)