เหตุใด ++ [[]] [+ []] + [+ []] ส่งคืนสตริง“ 10”


1657

สิ่งนี้ถูกต้องและส่งคืนสตริง"10"ใน JavaScript ( ตัวอย่างเพิ่มเติมที่นี่ ):

console.log(++[[]][+[]]+[+[]])

ทำไม? เกิดอะไรขึ้นที่นี่


446
เริ่มต้นด้วยความเข้าใจที่+[]0
ปลดแอก

14
ที่เกี่ยวข้องstackoverflow.com/questions/4170978/explain-why-this-works
Juho Vepsäläinen

10
ดูที่wtfjs.com - มันมีบางอย่างเช่นนั้นกับ explanatinos
ThiefMaster

3
@deceze คุณเรียนรู้สิ่งประเภทไหน? หนังสือเล่มไหน? ฉันกำลังเรียนรู้ JS จาก MDN และพวกเขาไม่ได้สอนสิ่งเหล่านี้
Siddharth Thevaril

6
@SiddharthThevaril แบบเดียวกับที่คุณเพิ่งทำ: มีคนโพสต์เกี่ยวกับที่แห่งนี้และฉันก็อ่านมัน
หลอกลวง

คำตอบ:


2070

ถ้าเราแยกมันออกระเบียบก็เท่ากับ:

++[[]][+[]]
+
[+[]]

ใน JavaScript +[] === 0มันเป็นความจริงว่า +แปลงบางอย่างเป็นตัวเลขและในกรณีนี้มันจะลงมา+""หรือ0(ดูรายละเอียดสเปคด้านล่าง)

ดังนั้นเราสามารถทำให้มันง่ายขึ้น ( ++มีความน่าเชื่อถือมากกว่า+):

++[[]][0]
+
[0]

เพราะ[[]][0]วิธีการ: รับองค์ประกอบแรกจาก[[]]มันเป็นความจริงที่:

[[]][0]ส่งคืนอาร์เรย์ภายใน ( []) เนื่องจากการอ้างอิงมันผิดที่จะพูด[[]][0] === []แต่เราเรียก array ภายในAเพื่อหลีกเลี่ยงการแสดงที่ผิด

++ก่อนที่ตัวถูกดำเนินการหมายถึง“ การเพิ่มขึ้นทีละตัวและส่งคืนผลลัพธ์ที่เพิ่มขึ้น” ดังนั้นจึง++[[]][0]เทียบเท่ากับNumber(A) + 1(หรือ+A + 1)

อีกครั้งเราสามารถทำให้ระเบียบง่ายขึ้นในสิ่งที่อ่านง่ายขึ้น ลองเปลี่ยน[]เป็นA:

(+[] + 1)
+
[0]

ก่อนที่จะ+[]สามารถบีบอัดอาร์เรย์ให้เป็นตัวเลข0ได้จะต้องทำการบีบอัดให้เป็นสตริงก่อนซึ่งก็คือ""อีกครั้ง สุดท้ายจะถูกเพิ่มซึ่งจะส่งผล11

  • (+[] + 1) === (+"" + 1)
  • (+"" + 1) === (0 + 1)
  • (0 + 1) === 1

มาทำให้มันง่ายขึ้นอีก:

1
+
[0]

นอกจากนี้สิ่งนี้เป็นจริงใน JavaScript: [0] == "0"เนื่องจากมันเข้าร่วมอาร์เรย์กับองค์ประกอบหนึ่ง เข้าร่วมจะ concatenate ,องค์ประกอบแยกจากกันโดย ด้วยองค์ประกอบเดียวคุณสามารถอนุมานได้ว่าตรรกะนี้จะส่งผลให้องค์ประกอบแรกนั้นเอง

ในกรณีนี้+จะเห็นตัวถูกดำเนินการสองตัวคือตัวเลขและอาร์เรย์ ตอนนี้กำลังพยายามบังคับให้ทั้งสองเป็นประเภทเดียวกัน ก่อนอื่นอาร์เรย์จะถูกรวมเข้ากับสตริง"0"จากนั้นตัวเลขจะถูกรวมเข้ากับสตริง ( "1") จำนวน+สตริง===สตริง

"1" + "0" === "10" // Yay!

ข้อกำหนดรายละเอียดสำหรับ+[]:

นี่เป็นเขาวงกต แต่+[]ก่อนอื่นต้องแปลงเป็นสตริงเพราะนั่นคือสิ่งที่+บอกว่า:

11.4.6 Unary + Operator

ตัวดำเนินการ unary + แปลงตัวถูกดำเนินการเป็นชนิด Number

การผลิต Unary Expression: + Unary Expression ได้รับการประเมินดังนี้:

  1. ให้ expr เป็นผลลัพธ์ของการประเมิน UnaryExpression

  2. Return ToNumber (GetValue (expr))

ToNumber() พูดว่า:

วัตถุ

ใช้ขั้นตอนต่อไปนี้:

  1. อนุญาตให้ primValue เป็น ToPrimitive (อาร์กิวเมนต์อินพุต, hint String)

  2. ส่งคืน ToString (primValue)

ToPrimitive() พูดว่า:

วัตถุ

ส่งคืนค่าเริ่มต้นสำหรับวัตถุ ค่าเริ่มต้นของวัตถุจะถูกดึงโดยเรียกวิธีการภายใน [[DefaultValue]] ของวัตถุผ่านคำแนะนำทางเลือก PreferredType พฤติกรรมของ [ภายในค่าเริ่มต้น] วิธีการภายในที่กำหนดไว้โดยข้อกำหนดนี้สำหรับวัตถุพื้นเมือง ECMAScript ใน 8.12.8

[[DefaultValue]] พูดว่า:

8.12.8 [[ค่าเริ่มต้น]] (คำใบ้)

เมื่อวิธีการภายใน [[ค่าเริ่มต้น]] ของ O ถูกเรียกด้วยคำใบ้สตริงจะดำเนินการตามขั้นตอนต่อไปนี้:

  1. ให้ toString เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Get]] ของ object O พร้อมอาร์กิวเมนต์ "toString"

  2. ถ้า IsCallable (toString) เป็นจริงแล้ว

ให้ str เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Call]] ของ toString โดยที่ O เป็นค่านี้และรายการอาร์กิวเมนต์ว่าง

ข ถ้า str เป็นค่าดั้งเดิมให้ส่งคืน str

.toStringของอาร์เรย์พูดว่า:

15.4.4.2 Array.prototype.toString ()

เมื่อเรียกใช้เมธอด toString จะมีการดำเนินการตามขั้นตอนต่อไปนี้:

  1. ให้อาร์เรย์เป็นผลลัพธ์ของการเรียก ToObject ในค่านี้

  2. ให้ func เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Get]] ของอาร์เรย์ที่มีอาร์กิวเมนต์ "เข้าร่วม"

  3. หาก IsCallable (func) เป็นเท็จให้ปล่อยให้ func เป็นเมธอดในตัว Object.prototype.toString (15.2.4.2) มาตรฐาน

  4. ส่งคืนผลลัพธ์ของการเรียกเมธอดภายใน [[Call]] ของ func ที่จัดเตรียมอาร์เรย์เป็นค่านี้และรายการอาร์กิวเมนต์ว่าง

ดังนั้น+[]ลงมาเพราะ+""[].join() === ""

อีกครั้ง+หมายถึง:

11.4.6 Unary + Operator

ตัวดำเนินการ unary + แปลงตัวถูกดำเนินการเป็นชนิด Number

การผลิต Unary Expression: + Unary Expression ได้รับการประเมินดังนี้:

  1. ให้ expr เป็นผลลัพธ์ของการประเมิน UnaryExpression

  2. Return ToNumber (GetValue (expr))

ToNumberถูกกำหนดให้""เป็น:

MV ของ StringNumericLiteral ::: [empty] คือ 0

ดังนั้นและทำให้+"" === 0+[] === 0


8
@ ฮาร์เปอร์: มันเป็นตัวตรวจสอบความเสมอภาคที่เข้มงวดนั่นคือมันจะส่งคืนก็ต่อtrueเมื่อทั้งค่าและประเภทเท่ากัน 0 == ""ส่งคืนtrue(เหมือนกันหลังจากการแปลงประเภท) แต่0 === ""เป็นfalse(ไม่ใช่ประเภทเดียวกัน)
pimvdb

41
ส่วนนี้ไม่ถูกต้อง นิพจน์เดือดลงไป1 + [0]ไม่ใช่"1" + [0]เนื่องจากตัวดำเนินการ prefix ( ++) ส่งคืนตัวเลขเสมอ ดูbclary.com/2004/11/07/#a-11.4.4
ทิมลง

6
@Tim Down: คุณถูกต้องอย่างสมบูรณ์ ฉันพยายามแก้ไขสิ่งนี้ แต่เมื่อพยายามจะทำฉันก็พบสิ่งอื่น ฉันไม่แน่ใจว่ามันเป็นไปได้อย่างไร ++[[]][0]ผลตอบแทนที่แน่นอน1แต่++[]โยนข้อผิดพลาด นี้เป็นที่น่าทึ่งเพราะดูเหมือนว่าจะต้มลงไป++[[]][0] ++[]คุณอาจมีความคิดใด ๆ ว่าทำไมจึง++[]เกิดข้อผิดพลาดในขณะที่++[[]][0]ไม่มี?
pimvdb

11
@pvdvdb: ฉันค่อนข้างมั่นใจว่าปัญหาอยู่ในการPutValueโทร (ในคำศัพท์ ES3, 8.7.2) ในการดำเนินการคำนำหน้า PutValueต้องมีการอ้างอิงในขณะ[]ที่การแสดงออกของตัวเองไม่ได้สร้างการอ้างอิง นิพจน์ที่มีการอ้างอิงตัวแปร (บอกว่าเราได้กำหนดไว้ก่อนหน้านี้var a = []แล้วใช้++aงานได้) หรือการเข้าถึงคุณสมบัติของวัตถุ (เช่น[[]][0]) ผลิตการอ้างอิง ในแง่ที่ง่ายกว่าตัวดำเนินการคำนำหน้าไม่เพียง แต่สร้างค่าเท่านั้น
Tim Down

13
@pimvdb: ดังนั้นหลังจากรันvar a = []; ++a, aคือ 1. หลังจากรัน++[[]][0]อาร์เรย์ที่สร้างขึ้นโดย[[]]การแสดงออกอยู่ในขณะนี้มีเพียงหมายเลข 1 ที่ดัชนี 0. ++ต้องอ้างอิงการทำเช่นนี้
Tim Down

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

จากนั้นเราก็มีการต่อสตริง

1+[0].toString() = 10

7
มันจะไม่ชัดเจนกว่าการเขียน===มากกว่า=>?
Mateen Ulhaq

61

ต่อไปนี้ถูกดัดแปลงจากโพสต์บล็อกที่ตอบคำถามที่ฉันโพสต์ในขณะที่คำถามนี้ยังคงปิดอยู่ ลิงค์ไปยัง (สำเนา HTML) ของข้อมูลจำเพาะ ECMAScript 3 ยังคงเป็นพื้นฐานสำหรับ JavaScript ในเว็บเบราว์เซอร์ที่ใช้กันทั่วไปในปัจจุบัน

ก่อนอื่นความเห็น: การแสดงออกชนิดนี้จะไม่ปรากฏในสภาพแวดล้อมการผลิตใด ๆ (สติ) และเป็นเพียงการใช้งานใด ๆ เป็นการออกกำลังกายในวิธีการที่ผู้อ่านรู้ดีว่า JavaScript สกปรก หลักการทั่วไปที่ผู้ประกอบการของ JavaScript ทำการแปลงโดยนัยระหว่างประเภทนั้นมีประโยชน์เช่นเดียวกับการแปลงทั่วไป แต่รายละเอียดส่วนใหญ่ในกรณีนี้ไม่ได้

การแสดงออก++[[]][+[]]+[+[]]ในตอนแรกอาจดูค่อนข้างน่าประทับใจและชัดเจน แต่จริงๆแล้วค่อนข้างง่ายที่จะแยกออกเป็นนิพจน์แยกกัน ด้านล่างฉันเพิ่งเพิ่มวงเล็บเพื่อความชัดเจน ฉันรับรองได้ว่าพวกเขาจะไม่เปลี่ยนแปลงอะไรเลย แต่ถ้าคุณต้องการตรวจสอบว่าคุณสามารถอ่านข้อมูลเกี่ยวกับผู้ดำเนินการจัดกลุ่มได้แล้ว ดังนั้นการแสดงออกสามารถเขียนได้ชัดเจนยิ่งขึ้นว่า

( ++[[]][+[]] ) + ( [+[]] )

หมดสภาพนี้เราสามารถลดความซับซ้อนโดยการสังเกตที่ประเมิน+[] 0เพื่อตอบสนองตัวเองว่าทำไมนี้เป็นจริงตรวจสอบผู้ประกอบการ + เอกและเดินไปตามเส้นทางคดเคี้ยวเล็กน้อยซึ่งจบลงด้วยToPrimitiveแปลงอาร์เรย์ที่ว่างเปล่าเป็นสตริงที่ว่างเปล่าซึ่งเป็นแล้วในที่สุดแปลง0โดยToNumber ตอนนี้เราสามารถแทนที่0แต่ละตัวอย่างของ+[]:

( ++[[]][0] ) + [0]

เรียบง่ายขึ้นแล้ว สำหรับ++[[]][0]นั่นคือการรวมกันของตัวดำเนินการเพิ่มคำนำหน้า ( ++) ตัวอักษรอาร์เรย์ที่กำหนดอาร์เรย์ที่มีองค์ประกอบเดียวที่ตัวเองเป็นอาร์เรย์ที่ว่างเปล่า ( [[]]) และตัวเข้าถึงคุณสมบัติ ( [0]) เรียกว่าในอาร์เรย์ที่กำหนดโดยตัวอักษรอาร์เรย์

ดังนั้นเราจึงสามารถลดความซับซ้อน[[]][0]เพียง[]และเรามี++[]ใช่มั้ย? ในความเป็นจริงแล้วนี่ไม่ใช่กรณีเนื่องจากการประเมิน++[]ข้อผิดพลาดเกิดขึ้นซึ่งในตอนแรกอาจดูสับสน อย่างไรก็ตามความคิดเล็กน้อยเกี่ยวกับลักษณะของการ++ทำให้สิ่งนี้ชัดเจน: มันใช้เพื่อเพิ่มตัวแปร (เช่น++i) หรือคุณสมบัติของวัตถุ (เช่น++obj.count) ไม่เพียง แต่ประเมินค่า แต่ยังเก็บค่านั้นไว้ที่อื่น ในกรณีของ++[]มันไม่มีที่ไหนเลยที่จะใส่ค่าใหม่ (สิ่งที่มันอาจจะเป็น) เพราะไม่มีการอ้างอิงถึงคุณสมบัติของวัตถุหรือตัวแปรที่จะปรับปรุง ในแง่ของข้อมูลจำเพาะสิ่งนี้ถูกครอบคลุมโดยการดำเนินการPutValueภายในซึ่งถูกเรียกใช้โดยตัวดำเนินการเพิ่มส่วนนำหน้า

ถ้าอย่างนั้นจะ++[[]][0]ทำอย่างไร? ดีโดยตรรกะที่คล้ายกันเป็น+[]แถวด้านในจะถูกแปลง0และค่านี้จะเพิ่มขึ้นโดยจะให้เราค่าสุดท้ายของ1 1มูลค่าของทรัพย์สินที่0อยู่ในแถวด้านนอกมีการปรับปรุงเพื่อและประเมินการแสดงออกทั้ง11

สิ่งนี้ทำให้เรามี

1 + [0]

... ซึ่งการใช้งานที่เรียบง่ายของผู้ประกอบการนอกจากนี้ ตัวถูกดำเนินการทั้งสองจะถูกแปลงเป็น primitivesเป็นอันดับแรกและหากค่าดั้งเดิมเป็นสตริงการดำเนินการต่อสตริงจะดำเนินการมิฉะนั้นจะมีการเพิ่มตัวเลข [0]แปรรูปเพื่อสตริงจะใช้ในการผลิต"0""10"

ท้ายที่สุดสิ่งที่อาจไม่ชัดเจนในทันทีก็คือการแทนที่หนึ่งในวิธีการtoString()หรือvalueOf()วิธีการArray.prototypeจะเปลี่ยนผลของการแสดงออกเพราะทั้งสองมีการตรวจสอบและใช้ถ้ามีอยู่เมื่อมีการแปลงวัตถุเป็นค่าดั้งเดิม ตัวอย่างเช่นต่อไปนี้

Array.prototype.toString = function() {
  return "foo";
};
++[[]][+[]]+[+[]]

... "NaNfoo"ผลิต ทำไมสิ่งนี้เกิดขึ้นเป็นแบบฝึกหัดสำหรับผู้อ่าน ...


24

มาทำให้เป็นเรื่องง่าย:

++[[]][+[]]+[+[]] = "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

อันนี้ประเมินไปเหมือนกัน แต่เล็กกว่านิดหน่อย

+!![]+''+(+[])
  • [] - เป็นอาร์เรย์ที่ถูกแปลงที่ถูกแปลงเป็น 0 เมื่อคุณเพิ่มหรือลบออกจากมันดังนั้น + [] = 0
  • ! [] - ประเมินว่าเป็นเท็จดังนั้นด้วยเหตุนี้ !! [] ประเมินผลเป็นจริง
  • + !! [] - แปลงค่าจริงเป็นค่าตัวเลขที่ประเมินค่าเป็นจริงดังนั้นในกรณีนี้ 1
  • + '' - ต่อท้ายสตริงที่ว่างเปล่ากับนิพจน์ทำให้ตัวเลขนั้นถูกแปลงเป็นสตริง
  • + [] - ประเมินเป็น 0

ดังนั้นประเมินว่า

+(true) + '' + (0)
1 + '' + 0
"10"

ดังนั้นตอนนี้คุณก็ลองใช้อันนี้:

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

ยังไม่มีการประเมินเป็น "10" อย่างไรก็ตามสิ่งนี้กำลังทำในวิธีที่แตกต่าง ลองประเมินสิ่งนี้ในตัวตรวจสอบจาวาสคริปต์เช่นโครมหรือบางอย่าง
Vlad Shlosberg

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

อันนี้ประเมินไปเหมือนกัน แต่มันก็เล็กกว่าของคุณด้วย:"10"
ADJenks

7

+ [] ประเมินเป็น 0 [... ] จากนั้นรวม (การดำเนินการ +) กับสิ่งใด ๆ ที่แปลงเนื้อหาอาร์เรย์เป็นการแสดงสตริงที่ประกอบด้วยองค์ประกอบที่เข้าร่วมกับเครื่องหมายจุลภาค

สิ่งอื่น ๆ เช่นการทำดัชนีของอาร์เรย์ (มีลำดับความสำคัญมากกว่าการดำเนินงาน +) เป็นลำดับและไม่มีอะไรน่าสนใจ


4

บางทีวิธีที่สั้นที่สุดในการประเมินค่านิพจน์เป็น "10" โดยไม่มีตัวเลขคือ

+!+[] + [+[]] // "10"

-~[] + [+[]] // "10"

// ========== คำอธิบาย ========== \\

+!+[]: +[]แปลงเป็น 0 แปรรูป!0 แปลงเป็น 1 = ซึ่งคือ 1true+true-~[]-(-1)

[+[]]: +[]แปลงเป็น 0 [0]คืออาร์เรย์ที่มีองค์ประกอบเดียว 0

จากนั้น JS ประเมินค่า1 + [0]ดังนั้นจึงNumber + Arrayแสดงออก จากนั้นข้อมูลจำเพาะของ ECMA จะทำงาน: +โอเปอเรเตอร์จะแปลงตัวถูกดำเนินการทั้งสองเป็นสตริงโดยการเรียกใช้toString()/valueOf()ฟังก์ชันจากObjectต้นแบบพื้นฐาน มันทำหน้าที่เป็นฟังก์ชั่นเสริมถ้าทั้งตัวถูกดำเนินการของการแสดงออกเป็นตัวเลขเท่านั้น เคล็ดลับคืออาร์เรย์สามารถแปลงองค์ประกอบของพวกเขาเป็นตัวแทนสตริงที่ตัดแบ่งได้อย่างง่ายดาย

ตัวอย่างบางส่วน:

1 + {} //    "1[object Object]"
1 + [] //    "1"
1 + new Date() //    "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"

มีข้อยกเว้นที่ดีซึ่งการObjectsเพิ่มสองรายการในNaN:

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

1
  1. สตริงที่มีค่าบวกและที่กำหนดจะแปลงเป็นตัวเลข
  2. ตัวดำเนินการส่วนเพิ่มที่ได้รับแปลงสตริงและเพิ่มทีละ 1
  3. [] == '' สตริงว่าง
  4. + '' หรือ + [] หาค่า 0

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10

1
คำตอบคือสับสน / สับสนฉันผิด []คือไม่ได้""เทียบเท่ากับ ++แรกองค์ประกอบที่สกัดแล้วแปลงโดย
แหลม

1

ทีละขั้นตอนของการ+เปลี่ยนมูลค่าเป็นตัวเลขและถ้าคุณเพิ่มไปยังอาร์เรย์ที่ว่างเปล่า+[]... เพราะมันว่างเปล่าและเท่ากับ0มันจะ

ดังนั้นจากที่นั่นตอนนี้มองเข้าไปในรหัสของคุณมัน++[[]][+[]]+[+[]]...

และมีการบวกระหว่างพวกเขา++[[]][+[]]+[+[]]

ดังนั้นสิ่งเหล่านี้[+[]]จะกลับมา[0]เนื่องจากมีอาเรย์ที่ว่างซึ่งถูกแปลงเป็น0ภายในอาเรย์อื่น ...

ตามจินตนาการแล้วค่าแรกคืออาร์เรย์2 มิติที่มีหนึ่งอาร์เรย์ภายใน ... ดังนั้น[[]][+[]]จะเท่ากับ[[]][0]ซึ่งจะส่งกลับ[]...

และในตอนท้าย++แปลงและเพิ่มเป็น1...

ดังนั้นคุณสามารถจินตนาการได้1+ "0"จะเป็น"10"...

ทำไมส่งคืนสตริง“ 10”

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.