สิ่งนี้ถูกต้องและส่งคืนสตริง"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
ได้จะต้องทำการบีบอัดให้เป็นสตริงก่อนซึ่งก็คือ""
อีกครั้ง สุดท้ายจะถูกเพิ่มซึ่งจะส่งผล1
1
(+[] + 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
อยู่ในแถวด้านนอกมีการปรับปรุงเพื่อและประเมินการแสดงออกทั้ง1
1
สิ่งนี้ทำให้เรามี
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