Tại sao ++ [[]] [+ []] + [+ []] trả về chuỗi 10 10 10?


1657

Đ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?


446
Bắt đầu bằng cách hiểu rằng +[]đưa ra một mảng trống để 0... sau đó lãng phí một buổi chiều ...;)
lừa dối


10
Hãy xem wtfjs.com - nó có khá nhiều thứ giống như vậy với Explatinos .
ThiefMaster

3
@deceze, bạn học loại công cụ đó ở đâu? Những cuốn sách nào? Tôi đang học JS từ MDN và họ không dạy những điều này
Siddharth Thevaril

6
@SiddharthThevaril Giống như cách bạn vừa làm: ai đó đã đăng về nó ở đâu đó và tôi tình cờ đọc nó.
lừa dối

Câu trả lời:


2071

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:

  1. Đặt expr là kết quả của việc đánh giá UnaryExpression.

  2. Trả về ToNumber (GetValue (expr)).

ToNumber() nói:

Vật

Áp dụng các bước sau:

  1. Đặt primValue là ToPrimitive (đối số đầu vào, chuỗi gợi ý).

  2. 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:

  1. Đặ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".

  2. 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 .toStringmả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:

  1. Đặt mảng là kết quả của việc gọi ToObject trên giá trị này.

  2. Đặ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".

  3. 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).

  4. 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:

  1. Đặt expr là kết quả của việc đánh giá UnaryExpression.

  2. 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.


8
@harper: Đó là trình kiểm tra đẳng thức nghiêm ngặt, nghĩa là nó chỉ trả về truenế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 === ""false(không phải cùng loại).
pimvdb

41
Một phần của điều này là không chính xác. Biểu thức sôi xuống 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
Tim Down

6
@Tim Down: Bạn hoàn toàn chính xác. Tôi đang cố gắng sửa nó, nhưng khi cố gắng làm vậy tôi đã tìm thấy thứ khác. Tôi không chắc làm thế nào điều này là có thể. ++[[]][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?
pimvdb

11
@pimvdb: Tôi khá chắc chắn rằng vấn đề nằm ở PutValuecuộc gọi (theo thuật ngữ ES3, 8.7.2) trong hoạt động tiền tố. PutValueyê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 = []đó ++ahoạ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ị đó.
Tim Down

13
@pimvdb: Vì vậy, sau khi thực hiện var a = []; ++a, alà 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.
Tim Down

124
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Sau đó, chúng tôi có một chuỗi nối

1+[0].toString() = 10

7
Nó sẽ không rõ ràng hơn để viết ===chứ không phải =>?
Mateen Ulhaq

61

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 0bằng ToNumber . Bây giờ chúng ta có thể thay thế 0cho 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 0và 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 0trong mảng bên ngoài được cập nhật 1và 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.prototypesẽ 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 ...


24

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"

13

Cái này đánh giá giống nhau nhưng nhỏ hơn một chút

+!![]+''+(+[])
  • [] - là một mảng được chuyển đổi được chuyển đổi thành 0 khi bạn thêm hoặc trừ nó, do đó + [] = 0
  • ! [] - đánh giá là sai, do đó !! [] đánh giá là đúng
  • + !! [] - chuyển đổi giá trị true thành giá trị số ước tính thành true, vì vậy trong trường hợp này 1
  • + '' - nối thêm một chuỗi trống vào biểu thức làm cho số được chuyển đổi thành chuỗi
  • + [] - ước tính thành 0

đá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:

_=$=+[],++_+''+$

À không, nó vẫn đánh giá là "10". Tuy nhiên điều này đang làm điều đó theo một cách khác. Hãy thử đánh giá điều này trong một trình kiểm tra javascript như chrome hoặc một cái gì đó.
Vlad Shlosberg

_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava

Cái này đánh giá giống nhau nhưng nó thậm chí còn nhỏ hơn của bạn:"10"
ADJenks

7

+ [] ướ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ị.


4

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. !0chuyển đổi thành true. +truechuyể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ừ Objectnguyê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 Objectskết quả bổ sung trong NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"

1
  1. Unary cộng với chuỗi đã cho chuyển đổi thành số
  2. Toán tử tăng cho phép chuyển đổi chuỗi và tăng thêm 1
  3. [] == ''. Chuỗi rỗng
  4. + '' 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

1
Câu trả lời là nhầm lẫn / khó hiểu, IOW sai. []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 ++.
PointedEars

1

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 0bê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"...

Tại sao lại trả về chuỗi 10 số 10?

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.