สิ่งนี้ถูกต้องและส่งคืนสตริง"10"ใน JavaScript ( ตัวอย่างเพิ่มเติมที่นี่ ):
console.log(++[[]][+[]]+[+[]])
ทำไม? เกิดอะไรขึ้นที่นี่
สิ่งนี้ถูกต้องและส่งคืนสตริง"10"ใน JavaScript ( ตัวอย่างเพิ่มเติมที่นี่ ):
console.log(++[[]][+[]]+[+[]])
ทำไม? เกิดอะไรขึ้นที่นี่
คำตอบ:
ถ้าเราแยกมันออกระเบียบก็เท่ากับ:
++[[]][+[]]
+
[+[]]
ใน 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 ได้รับการประเมินดังนี้:
ให้ expr เป็นผลลัพธ์ของการประเมิน UnaryExpression
Return ToNumber (GetValue (expr))
ToNumber() พูดว่า:
วัตถุ
ใช้ขั้นตอนต่อไปนี้:
อนุญาตให้ primValue เป็น ToPrimitive (อาร์กิวเมนต์อินพุต, hint String)
ส่งคืน ToString (primValue)
ToPrimitive() พูดว่า:
วัตถุ
ส่งคืนค่าเริ่มต้นสำหรับวัตถุ ค่าเริ่มต้นของวัตถุจะถูกดึงโดยเรียกวิธีการภายใน [[DefaultValue]] ของวัตถุผ่านคำแนะนำทางเลือก PreferredType พฤติกรรมของ [ภายในค่าเริ่มต้น] วิธีการภายในที่กำหนดไว้โดยข้อกำหนดนี้สำหรับวัตถุพื้นเมือง ECMAScript ใน 8.12.8
[[DefaultValue]] พูดว่า:
8.12.8 [[ค่าเริ่มต้น]] (คำใบ้)
เมื่อวิธีการภายใน [[ค่าเริ่มต้น]] ของ O ถูกเรียกด้วยคำใบ้สตริงจะดำเนินการตามขั้นตอนต่อไปนี้:
ให้ toString เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Get]] ของ object O พร้อมอาร์กิวเมนต์ "toString"
ถ้า IsCallable (toString) เป็นจริงแล้ว
ให้ str เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Call]] ของ toString โดยที่ O เป็นค่านี้และรายการอาร์กิวเมนต์ว่าง
ข ถ้า str เป็นค่าดั้งเดิมให้ส่งคืน str
.toStringของอาร์เรย์พูดว่า:
15.4.4.2 Array.prototype.toString ()
เมื่อเรียกใช้เมธอด toString จะมีการดำเนินการตามขั้นตอนต่อไปนี้:
ให้อาร์เรย์เป็นผลลัพธ์ของการเรียก ToObject ในค่านี้
ให้ func เป็นผลลัพธ์ของการเรียกเมธอดภายใน [[Get]] ของอาร์เรย์ที่มีอาร์กิวเมนต์ "เข้าร่วม"
หาก IsCallable (func) เป็นเท็จให้ปล่อยให้ func เป็นเมธอดในตัว Object.prototype.toString (15.2.4.2) มาตรฐาน
ส่งคืนผลลัพธ์ของการเรียกเมธอดภายใน [[Call]] ของ func ที่จัดเตรียมอาร์เรย์เป็นค่านี้และรายการอาร์กิวเมนต์ว่าง
ดังนั้น+[]ลงมาเพราะ+""[].join() === ""
อีกครั้ง+หมายถึง:
11.4.6 Unary + Operator
ตัวดำเนินการ unary + แปลงตัวถูกดำเนินการเป็นชนิด Number
การผลิต Unary Expression: + Unary Expression ได้รับการประเมินดังนี้:
ให้ expr เป็นผลลัพธ์ของการประเมิน UnaryExpression
Return ToNumber (GetValue (expr))
ToNumberถูกกำหนดให้""เป็น:
MV ของ StringNumericLiteral ::: [empty] คือ 0
ดังนั้นและทำให้+"" === 0+[] === 0
trueเมื่อทั้งค่าและประเภทเท่ากัน 0 == ""ส่งคืนtrue(เหมือนกันหลังจากการแปลงประเภท) แต่0 === ""เป็นfalse(ไม่ใช่ประเภทเดียวกัน)
1 + [0]ไม่ใช่"1" + [0]เนื่องจากตัวดำเนินการ prefix ( ++) ส่งคืนตัวเลขเสมอ ดูbclary.com/2004/11/07/#a-11.4.4
++[[]][0]ผลตอบแทนที่แน่นอน1แต่++[]โยนข้อผิดพลาด นี้เป็นที่น่าทึ่งเพราะดูเหมือนว่าจะต้มลงไป++[[]][0] ++[]คุณอาจมีความคิดใด ๆ ว่าทำไมจึง++[]เกิดข้อผิดพลาดในขณะที่++[[]][0]ไม่มี?
PutValueโทร (ในคำศัพท์ ES3, 8.7.2) ในการดำเนินการคำนำหน้า PutValueต้องมีการอ้างอิงในขณะ[]ที่การแสดงออกของตัวเองไม่ได้สร้างการอ้างอิง นิพจน์ที่มีการอ้างอิงตัวแปร (บอกว่าเราได้กำหนดไว้ก่อนหน้านี้var a = []แล้วใช้++aงานได้) หรือการเข้าถึงคุณสมบัติของวัตถุ (เช่น[[]][0]) ผลิตการอ้างอิง ในแง่ที่ง่ายกว่าตัวดำเนินการคำนำหน้าไม่เพียง แต่สร้างค่าเท่านั้น
var a = []; ++a, aคือ 1. หลังจากรัน++[[]][0]อาร์เรย์ที่สร้างขึ้นโดย[[]]การแสดงออกอยู่ในขณะนี้มีเพียงหมายเลข 1 ที่ดัชนี 0. ++ต้องอ้างอิงการทำเช่นนี้
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
จากนั้นเราก็มีการต่อสตริง
1+[0].toString() = 10
===มากกว่า=>?
ต่อไปนี้ถูกดัดแปลงจากโพสต์บล็อกที่ตอบคำถามที่ฉันโพสต์ในขณะที่คำถามนี้ยังคงปิดอยู่ ลิงค์ไปยัง (สำเนา 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"ผลิต ทำไมสิ่งนี้เกิดขึ้นเป็นแบบฝึกหัดสำหรับผู้อ่าน ...
มาทำให้เป็นเรื่องง่าย:
++[[]][+[]]+[+[]] = "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"
อันนี้ประเมินไปเหมือนกัน แต่เล็กกว่านิดหน่อย
+!![]+''+(+[])
ดังนั้นประเมินว่า
+(true) + '' + (0)
1 + '' + 0
"10"
ดังนั้นตอนนี้คุณก็ลองใช้อันนี้:
_=$=+[],++_+''+$
"10"
+ [] ประเมินเป็น 0 [... ] จากนั้นรวม (การดำเนินการ +) กับสิ่งใด ๆ ที่แปลงเนื้อหาอาร์เรย์เป็นการแสดงสตริงที่ประกอบด้วยองค์ประกอบที่เข้าร่วมกับเครื่องหมายจุลภาค
สิ่งอื่น ๆ เช่นการทำดัชนีของอาร์เรย์ (มีลำดับความสำคัญมากกว่าการดำเนินงาน +) เป็นลำดับและไม่มีอะไรน่าสนใจ
บางทีวิธีที่สั้นที่สุดในการประเมินค่านิพจน์เป็น "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]"
+ '' หรือ + [] หาค่า 0
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10[]คือไม่ได้""เทียบเท่ากับ ++แรกองค์ประกอบที่สกัดแล้วแปลงโดย
ทีละขั้นตอนของการ+เปลี่ยนมูลค่าเป็นตัวเลขและถ้าคุณเพิ่มไปยังอาร์เรย์ที่ว่างเปล่า+[]... เพราะมันว่างเปล่าและเท่ากับ0มันจะ
ดังนั้นจากที่นั่นตอนนี้มองเข้าไปในรหัสของคุณมัน++[[]][+[]]+[+[]]...
และมีการบวกระหว่างพวกเขา++[[]][+[]]+[+[]]
ดังนั้นสิ่งเหล่านี้[+[]]จะกลับมา[0]เนื่องจากมีอาเรย์ที่ว่างซึ่งถูกแปลงเป็น0ภายในอาเรย์อื่น ...
ตามจินตนาการแล้วค่าแรกคืออาร์เรย์2 มิติที่มีหนึ่งอาร์เรย์ภายใน ... ดังนั้น[[]][+[]]จะเท่ากับ[[]][0]ซึ่งจะส่งกลับ[]...
และในตอนท้าย++แปลงและเพิ่มเป็น1...
ดังนั้นคุณสามารถจินตนาการได้1+ "0"จะเป็น"10"...
+[]0