ทำไม 2 == [2] ใน JavaScript


164

ฉันเพิ่งค้นพบว่า2 == [2]ใน JavaScript เมื่อมันปรากฏออกมาการเล่นโวหารนี้มีผลที่น่าสนใจสองประการ

var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

ในทำนองเดียวกันงานต่อไปนี้:

var a = { "abc" : 1 };
a[["abc"]] === a["abc"]; // this is also true

แม้กระทั่งคนแปลกหน้าก็ใช้งานได้เช่นกัน:

[[[[[[[2]]]]]]] == 2; // this is true too! WTF?

พฤติกรรมเหล่านี้ดูเหมือนจะสอดคล้องกันในทุกเบราว์เซอร์

ความคิดใดว่าทำไมนี่คือคุณสมบัติภาษา?

นี่คือผลที่ตามมาอย่างบ้าคลั่งของ "ฟีเจอร์" นี้:

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

var a = [0];
a == a // true
a == !a // also true, WTF?

ตัวอย่างเหล่านี้ถูกค้นพบโดย jimbojw http://jimbojw.comชื่อเสียงเช่นเดียวกับwalkingeyerobot

คำตอบ:


134

คุณสามารถค้นหาอัลกอริธึมการเปรียบเทียบในข้อมูลจำเพาะ ECMA (ส่วนที่เกี่ยวข้องของ ECMA-262, ฉบับที่ 3 สำหรับปัญหาของคุณ: 11.9.3, 9.1, 8.6.2.6)

หากคุณแปลอัลกอริธึมนามธรรมที่เกี่ยวข้องกลับไปที่ JS สิ่งที่เกิดขึ้นเมื่อการประเมิน2 == [2]นั้นเป็นแบบนี้

2 === Number([2].valueOf().toString())

โดยที่valueOf()อาร์เรย์ส่งคืนอาร์เรย์เองและการแทนค่าสตริงของอาร์เรย์แบบองค์ประกอบเดียวเป็นการแทนสตริงขององค์ประกอบเดียว

นอกจากนี้ยังอธิบายตัวอย่างที่สามยังคงเป็นเพียงสตริง[[[[[[[2]]]]]]].toString()2

อย่างที่คุณเห็นมันมีเวทมนตร์อยู่เบื้องหลังจำนวนมากที่เกี่ยวข้องซึ่งเป็นสาเหตุที่ฉันมักจะใช้ตัวดำเนินการความเท่าเทียมอย่างเข้มงวด===เท่านั้น

ตัวอย่างแรกและครั้งที่สองนั้นง่ายต่อการติดตามเนื่องจากชื่อคุณสมบัติเป็นสตริงเสมอ

a[[2]]

เทียบเท่ากับ

a[[2].toString()]

ซึ่งเป็นเพียง

a["2"]

โปรดทราบว่าแม้กระทั่งคีย์ตัวเลขจะถือว่าเป็นชื่อคุณสมบัติ (เช่นสตริง) ก่อนที่อาเรย์มายากลจะเกิดขึ้น


10

เป็นเพราะการแปลงประเภทโดยนัยของ==โอเปอเรเตอร์

[2] ถูกแปลงเป็น Number คือ 2 เมื่อเปรียบเทียบกับ Number ลองใช้+โอเปอเรเตอร์unary ใน [2]

> +[2]
2

คนอื่นบอกว่า [2] ถูกแปลงเป็นสตริง +"2"ยังเป็นหมายเลข 2
dlamblin

1
จริงๆแล้วมันไม่ง่ายเลย [2] ถูกแปลงเป็นสตริงจะใกล้เคียงกว่า แต่ดูที่ecma-international.org/ecma-262/5.1/#sec-11.9.3
neo

10
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

ทางด้านขวาของสมการเรามี [2] ซึ่งส่งกลับชนิดตัวเลขที่มีค่า 2 ทางด้านซ้ายเราจะสร้างอาร์เรย์ใหม่โดยมีวัตถุเดียวเป็น 2 จากนั้นเราจะเรียก [( อาร์เรย์อยู่ที่นี่)] ฉันไม่แน่ใจว่าสิ่งนี้ประเมินเป็นสตริงหรือตัวเลข 2 หรือ "2" ให้นำกรณีสตริงมาก่อน ฉันเชื่อว่า ["2"] จะสร้างตัวแปรใหม่และส่งคืนค่าว่าง null! == 2. ลองสมมติว่าจริง ๆ แล้วแปลงเป็นตัวเลขโดยปริยาย [2] จะส่งคืนการจับคู่ประเภท 2 2 และ 2 (= === ทำงาน) และความคุ้มค่า ฉันคิดว่ามันเป็นการแปลงอาเรย์เป็นตัวเลขโดยปริยายเพราะ [ค่า] คาดว่าจะเป็นสตริงหรือตัวเลข ดูเหมือนว่าจำนวนจะมีความสำคัญสูงกว่า

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

var a = { "abc" : 1 };
a[["abc"]] === a["abc"];

ในตัวอย่างนี้คุณกำลังสร้างวัตถุที่เรียกว่า a กับสมาชิกที่เรียกว่า abc ด้านขวาของสมการค่อนข้างง่าย มันเทียบเท่ากับ a.abc สิ่งนี้จะส่งกลับ 1 ด้านซ้ายก่อนจะสร้างอาร์เรย์ที่แท้จริงของ ["abc"] จากนั้นคุณค้นหาตัวแปรบนวัตถุโดยผ่านในอาร์เรย์ที่สร้างขึ้นใหม่ ตั้งแต่นี้คาดว่าสตริงมันจะแปลงอาร์เรย์เป็นสตริง ตอนนี้ประเมินเป็น ["abc"] ซึ่งเท่ากับ 1 1 และ 1 เป็นชนิดเดียวกัน (ซึ่งเป็นสาเหตุที่ === ทำงาน) และค่าเท่ากัน

[[[[[[[2]]]]]]] == 2; 

นี่เป็นเพียงการแปลงโดยนัย === จะไม่ทำงานในสถานการณ์นี้เพราะมีประเภทที่ไม่ตรงกัน


คำตอบสำหรับคำถามของคุณเกี่ยวกับความสำคัญ: ==ใช้ToPrimitive()อาร์เรย์ซึ่งจะเรียกมันtoString()วิธีดังนั้นสิ่งที่คุณเปรียบเทียบจริงเป็นจำนวน2สตริง"2"; การเปรียบเทียบระหว่างสตริงและตัวเลขทำได้โดยการแปลงสตริง
Christoph

8

สำหรับ==กรณีนี้คือเหตุผลที่ดั๊กครอก===แนะนำเสมอโดยใช้ มันไม่ทำการแปลงประเภทโดยนัยใด ๆ

สำหรับตัวอย่างที่มี===การแปลงชนิดโดยนัยจะกระทำก่อนที่จะเรียกตัวดำเนินการความเท่าเทียมกัน


7
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

นั่นน่าสนใจไม่ใช่ว่า [0] ทั้งจริงและเท็จจริง

[0] == true // false

มันเป็นวิธีที่ตลกของจาวาสคริปต์ในการประมวลผล if () ตัวดำเนินการ


4
ที่จริงมันเป็นวิธีที่ตลก==งาน; ถ้าคุณใช้หล่ออย่างชัดเจนที่เกิดขึ้นจริง (เช่นBoolean([0])หรือ!![0]) คุณจะพบว่า[0]จะมีการประเมินไปtrueในบริบทแบบบูลตามที่ควรใน JS วัตถุใด ๆ ถือว่าtrue
คริสโต

6

อาร์เรย์ของไอเท็มหนึ่งสามารถใช้เป็นไอเท็มได้

นี่เป็นเพราะการพิมพ์เป็ด ตั้งแต่ "2" == 2 == [2] และอาจมากกว่านั้น


4
เพราะพวกเขาไม่ตรงกับประเภท ในตัวอย่างแรกด้านซ้ายจะถูกประเมินก่อนและพวกมันจะจับคู่กันในรูปแบบ
Shawn

8
นอกจากนี้ฉันไม่คิดว่าการพิมพ์เป็ดเป็นคำที่ถูกต้องที่นี่ มันเป็นเรื่องเกี่ยวกับการแปลงประเภทโดยนัยที่ดำเนินการโดย==ผู้ดำเนินการก่อนเปรียบเทียบ
Chetan S

14
นี้มีอะไรจะทำอย่างไรกับเป็ดพิมพ์ แต่กับการพิมพ์ที่อ่อนแอเช่นแปลงชนิดนัย
คริสโต

@Chetan: สิ่งที่เขาพูด;)
คริสโต

2
สิ่งที่ Chetan และ Christoph พูด
ทิมลง

3

เพื่อเพิ่มรายละเอียดเล็ก ๆ น้อย ๆ ให้คำตอบอื่น ๆ ... เมื่อเปรียบเทียบArrayไปNumber, Javascript จะแปลงด้วยArray parseFloat(array)คุณสามารถลองด้วยตัวเองในคอนโซล (เช่น Firebug หรือ Web Inspector) เพื่อดูว่ามีการArrayแปลงค่าที่แตกต่างกันอย่างไร

parseFloat([2]); // 2
parseFloat([2, 3]); // 2
parseFloat(['', 2]); // NaN

สำหรับArrayS, parseFloatทำการดำเนินการในที่Arrayของสมาชิกคนแรกและทิ้งส่วนที่เหลือ

แก้ไข: ตามรายละเอียดของ Christoph อาจเป็นได้ว่าใช้รูปแบบที่ยาวขึ้นภายใน แต่ผลลัพธ์นั้นเหมือนกันเสมอparseFloatดังนั้นคุณสามารถใช้parseFloat(array)ชวเลขเพื่อรู้ว่าจะแปลงเป็นอย่างไร


2

คุณกำลังเปรียบเทียบวัตถุ 2 รายการในทุกกรณีอย่าใช้ == หากคุณกำลังคิดที่จะเปรียบเทียบคุณกำลังมี === อยู่ในใจและไม่ == == มักจะให้ผลเสียสติ มองหาส่วนที่ดีในภาษา :)


0

คำอธิบายสำหรับส่วนแก้ไขของคำถาม:

ตัวอย่างที่ 1

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

First typecast [0] ถึงค่าดั้งเดิมตามคำตอบของ Christoph ด้านบนเรามี "0" ( [0].valueOf().toString())

"0" == false

ตอนนี้ typecast Boolean (false) ถึง Number จากนั้น String ("0") ถึง Number

Number("0") == Number(false)
or  0 == 0 
so, [0] == false  // true

สำหรับifคำสั่งถ้าไม่มีการเปรียบเทียบอย่างชัดเจนในตัวเองถ้าเงื่อนไขสภาพประเมินสำหรับtruthyค่า

มีค่าเท็จเพียง6 ค่า : false, null, undefined, 0, NaN และสตริงว่าง "" และสิ่งใดก็ตามที่ไม่ใช่ค่าเท็จก็คือคุณค่าที่แท้จริง

เนื่องจาก [0] ไม่ใช่ค่าที่ไม่ถูกต้องมันเป็นค่าifความจริงดังนั้นข้อความจึงประเมินว่าเป็นจริง & ดำเนินการคำสั่ง


ตัวอย่างที่ 2

var a = [0];
a == a // true
a == !a // also true, WTF?

พิมพ์การหล่อค่าให้เป็นแบบดั้งเดิมอีกครั้ง

    a = a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == "0" // true; same type, same value


a == !a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == !"0"
or  "0" == false
or  Number("0") == Number(false)
or  0 = 0   // true
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.