อาร์เรย์ที่ว่างเปล่าดูเหมือนจะเท่ากันจริงและเท็จในเวลาเดียวกัน


201

อาร์เรย์ที่ว่างเปล่านั้นเป็นจริง แต่พวกมันก็เท่ากับเท็จ

var arr = [];
console.log('Array:', arr);
if (arr) console.log("It's true!");
if (arr == false) console.log("It's false!");
if (arr && arr == false) console.log("...what??");

ฉันเดาว่านี่เป็นเพราะการแปลงโดยนัยซึ่งดำเนินการโดยผู้ดำเนินการความเท่าเทียมกัน

มีใครอธิบายได้บ้างว่าเกิดอะไรขึ้นเบื้องหลัง?


1
นี่คือกระทู้ที่คล้ายกันซึ่งควรจะทำให้เข้าใจถึงปัญหา: stackoverflow.com/questions/4226101/…
Rion Williams

2
โปรดทราบarr == trueว่าไม่ได้ประเมินว่าเป็นจริง ;-)
Michael Krelin - แฮ็กเกอร์

5
ว้าว ... เมื่อคุณคิดว่าคุณทำสิ่งเหล่านี้หมดแล้ว
harpo

3
เพื่อหลีกเลี่ยงการ Javascript ประเภทบังคับ WTF ใช้ ===opeartor ถ้าคุณต้องการทดสอบความว่างของอาเรย์ให้ใช้arr === []
DjebbZ

17
หากคุณต้องการทดสอบความว่างเปล่าของอาเรย์ไม่ควรใช้arr === []เพราะจะส่งคืนค่าเท็จเนื่องจากด้านขวาจะสร้างอาเรย์ใหม่ทันทีและตัวแปรทางด้านซ้ายไม่สามารถอ้างถึงสิ่งที่คุณเพิ่งสร้างขึ้น arr.length === 0การทดสอบความว่างเปล่าควรจะทำโดยการมองขึ้น
Kyle Baker

คำตอบ:


274

คุณกำลังทดสอบสิ่งต่าง ๆ ที่นี่

if (arr) เรียกบนวัตถุ (Array เป็นอินสแตนซ์ของ Object ใน JS) จะตรวจสอบว่ามีวัตถุนั้นอยู่หรือไม่และคืนค่าจริง / เท็จ

เมื่อคุณเรียกif (arr == false)คุณเปรียบเทียบค่าของวัตถุนี้และfalseค่าดั้งเดิม ภายในเรียกว่าซึ่งจะส่งกลับสตริงที่ว่างเปล่าarr.toString()""

นี่เป็นเพราะการtoStringเรียกคืนค่า Array Array.join()และสตริงว่างเป็นหนึ่งในค่าที่ผิดพลาดใน JavaScript


2
คุณช่วยอธิบายได้ไหมว่าทำไมBoolean([])ผลตอบแทน true
Devy

11
โดยการประชุมใน JS หากวัตถุถูกบีบบังคับให้บูลีนพวกมันจะถูกบังคับให้เป็น TRUE เสมอ ดูตาราง "บริบทบูลีน" ได้ที่: javascript.info/tutorial/object-conversion
Niki

2
@Devy วัตถุทั้งหมดใน JavaScript นั้นเป็นจริงดังนั้นการแปลงวัตถุใด ๆ เป็นบูลีนเป็นจริง ดู2ality.com/2013/08/objects-truthy.html
Thomson

62

เกี่ยวกับสาย:

if (arr == false) console.log("It's false!");

บางทีสิ่งเหล่านี้จะช่วย:

console.log(0 == false) // true
console.log([] == 0) // true
console.log([] == "") // true

สิ่งที่ฉันเชื่อว่าเกิดขึ้นคือบูลีนfalseถูกบีบบังคับให้0เปรียบเทียบกับวัตถุ (ด้านซ้ายมือ) วัตถุถูกบีบอัดให้เป็นสตริง (สตริงว่าง) จากนั้นสตริงว่างจะถูกรวมเข้ากับตัวเลขเช่นกันคือศูนย์ และเพื่อให้การเปรียบเทียบสุดท้ายคือ0== ซึ่งเป็น0true

แก้ไข:ดูส่วนนี้ของข้อมูลจำเพาะสำหรับรายละเอียดเกี่ยวกับวิธีการทำงาน

นี่คือสิ่งที่เกิดขึ้นเริ่มต้นที่กฎ # 1:

1. หากประเภท (x) แตกต่างจากประเภท (y) ให้ไปที่ขั้นตอนที่ 14

กฎต่อไปที่ใช้คือ # 19:

19. ถ้า Type (y) เป็น Boolean ให้ส่งคืนผลลัพธ์ของการเปรียบเทียบ x == ToNumber (y)

ผลลัพธ์ของToNumber(false)คือ0ตอนนี้เรามี:

[] == 0

อีกครั้งกฎ # 1 บอกให้เราข้ามไปที่ขั้นตอนที่ 14 แต่ขั้นตอนต่อไปที่ใช้จริงคือ # 21:

21. ถ้า Type (x) เป็น Object และ Type (y) เป็น String หรือ Number ให้ส่งคืนผลลัพธ์ของการเปรียบเทียบ ToPrimitive (x) == y

ผลของการ ToPrimitive([])คือสตริงว่างดังนั้นตอนนี้เรามี:

"" == 0

อีกครั้งกฎ # 1 บอกให้เราข้ามไปที่ขั้นตอนที่ 14 แต่ขั้นตอนต่อไปที่ใช้จริงคือ # 17:

17. ถ้า Type (x) เป็น String และ Type (y) เป็น Number ให้ส่งคืนผลลัพธ์ของการเปรียบเทียบ ToNumber (x) == y

ผลที่ได้ToNumber("")คือ0ทำให้เรามี:

0 == 0

ตอนนี้ค่าทั้งสองมีประเภทเดียวกันดังนั้นขั้นตอนต่อจาก # 1 ถึง # 7 ซึ่งกล่าวว่า:

7. ถ้า x เป็นค่าตัวเลขเดียวกับ y ให้ส่งคืนจริง

ดังนั้นเรากลับมา trueดังนั้นเรากลับมา

โดยย่อ:

ToNumber(ToPrimitive([])) == ToNumber(false)

2
การอ้างอิงที่ดี! เพื่อหลีกเลี่ยงความสับสนอาจเป็นประโยชน์ในการกล่าวถึงเหตุผลที่ว่า "กฎข้อถัดไปที่ใช้คือ # 19" แม้ว่ากฎ # 1 จะบอกว่า "ไปที่ขั้นตอนที่ 14" ก็เป็นเพราะขั้นตอนที่ 14-18 ไม่ตรงกับประเภทของ ค่าที่ถูกเปรียบเทียบ
Sean the Bean

2
คำอธิบายที่ดี มันทำให้ฉันงงงวยว่าอาร์เรย์ที่ว่างเปล่าถูกพิจารณาว่าเป็นความจริง 0 คือเท็จและยัง[] == 0เป็นจริง ฉันได้รับสิ่งนี้เกิดขึ้นตามคำอธิบายของคุณของ spec แต่ดูเหมือนพฤติกรรมภาษาแปลก ๆ จากมุมมองเชิงตรรกะ
bigh_29

7

เพื่อเสริมคำตอบของ Wayneและพยายามอธิบายว่าทำไมToPrimitive([])ผลตอบแทน""มันคุ้มค่าที่จะพิจารณาคำตอบที่เป็นไปได้สองแบบสำหรับคำถาม 'ทำไม' ประเภทแรกของคำตอบคือ: "เพราะสเปคบอกว่านี่เป็นวิธีที่ JavaScript จะทำงาน" ในข้อมูลจำเพาะ ES5 ส่วน 9.1ซึ่งอธิบายผลลัพธ์ของ ToPrimitive เป็นค่าเริ่มต้นสำหรับวัตถุ:

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

ส่วน 8.12.8อธิบาย[[DefaultValue]]วิธีการ วิธีนี้ใช้ "คำใบ้" เป็นอาร์กิวเมนต์และคำใบ้อาจเป็นได้ทั้งสตริงหรือจำนวน เพื่อให้ง่ายต่อเรื่องนี้โดยการจ่ายยาที่มีรายละเอียดบางอย่างถ้าคำใบ้คือ String แล้ว[[DefaultValue]]ส่งกลับค่าของtoString()ถ้ามันมีอยู่และส่งกลับค่าดั้งเดิมและอื่น ๆ valueOf()ส่งกลับค่าของ หากคำใบ้คือ Number ลำดับความสำคัญของtoString()และvalueOf()จะถูกย้อนกลับเพื่อvalueOf()เรียกว่าลำดับแรกและค่าจะถูกส่งกลับหากเป็นค่าดั้งเดิม ดังนั้นไม่ว่าจะ[[DefaultValue]]ส่งคืนผลลัพธ์toString()หรือvalueOf()ขึ้นอยู่กับ PreferredType ที่ระบุสำหรับวัตถุและฟังก์ชันเหล่านี้จะคืนค่าดั้งเดิมหรือไม่

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

เพื่ออ้างอิงJavaScriptของ David Flanagan : คู่มือขั้นสุดท้ายซึ่งเป็นหนังสือที่ยอดเยี่ยมที่ควรจะเป็นที่แรกที่ทุกคนจะได้รับคำตอบสำหรับคำถามประเภทนี้:

รายละเอียดของการแปลงออบเจ็กต์เป็นตัวเลขอธิบายว่าทำไมอาเรย์ที่ว่างเปล่าแปลงเป็นตัวเลข 0 และสาเหตุที่อาเรย์ที่มีองค์ประกอบเดียวอาจแปลงเป็นตัวเลข อาร์เรย์สืบทอดเมธอด valueOf () เริ่มต้นที่ส่งคืนออบเจ็กต์แทนที่จะเป็นค่าดั้งเดิมดังนั้นการแปลงอาเรย์เป็นตัวเลขจึงอาศัยเมธอด toString () อาร์เรย์ที่ว่างเปล่าแปลงเป็นสตริงว่าง และสตริงว่างจะแปลงเป็นตัวเลข 0 อาร์เรย์ที่มีองค์ประกอบเดียวจะแปลงเป็นสตริงเดียวกันกับที่องค์ประกอบหนึ่งทำ ถ้าอาร์เรย์มีหมายเลขเดียวตัวเลขนั้นจะถูกแปลงเป็นสตริงจากนั้นกลับไปเป็นตัวเลข

คำตอบประเภทที่สองของคำถาม "ทำไม" นอกเหนือจาก "เพราะข้อมูลจำเพาะบอกว่า" ให้คำอธิบายว่าทำไมพฤติกรรมจึงสมเหตุสมผลจากมุมมองการออกแบบ ในปัญหานี้ฉันสามารถเก็งกำไรเท่านั้น ก่อนอื่นเราจะแปลงอาเรย์เป็นตัวเลขได้อย่างไร? ความเป็นไปได้ที่สมเหตุสมผลเพียงอย่างเดียวที่ฉันคิดได้คือการแปลงอาเรย์ว่างเป็น 0 และอาเรย์ที่ไม่ว่างใด ๆ เป็น 1 แต่เมื่อคำตอบของเวย์เผยให้เห็น นอกเหนือจากนี้มันเป็นเรื่องยากที่จะคิดว่าค่าส่งคืนดั้งเดิมที่สมเหตุสมผลสำหรับ Array.valueOf () ดังนั้นเราอาจโต้แย้งว่ามันสมเหตุสมผลกว่าที่จะArray.valueOf()เป็นค่าเริ่มต้นและคืน Array เองกลับมาtoString()ไปสู่ผลลัพธ์ที่ใช้โดย ToPrimitive มันเหมาะสมกว่าที่จะแปลง Array ให้เป็นสตริงแทนที่จะเป็นตัวเลข

ยิ่งไปกว่านั้นตามคำพูดของฟลานาแกนที่บอกเป็นนัย ๆ การตัดสินใจออกแบบนี้ช่วยให้เกิดพฤติกรรมที่เป็นประโยชน์บางประเภท ตัวอย่างเช่น

var a = [17], b = 17, c=1;
console.log(a==b);      // <= true
console.log(a==c);      // <= false

พฤติกรรมนี้ช่วยให้คุณสามารถเปรียบเทียบอาร์เรย์องค์ประกอบเดียวกับตัวเลขและรับผลลัพธ์ที่คาดหวัง


ขอบคุณสำหรับคำตอบนี้เป็นคำอธิบายโดยละเอียดที่ค่อนข้างขาดคำถาม
Estus Flask

3
console.log('-- types: undefined, boolean, number, string, object --');
console.log(typeof undefined);  // undefined
console.log(typeof null);       // object
console.log(typeof NaN);        // number
console.log(typeof false);      // boolean
console.log(typeof 0);          // number
console.log(typeof "");         // string
console.log(typeof []);         // object
console.log(typeof {});         // object

console.log('-- Different values: NotExist, Falsy, NaN, [], {} --');
console.log('-- 1. NotExist values: undefined, null have same value --');
console.log(undefined == null); // true

console.log('-- 2. Falsy values: false, 0, "" have same value --');
console.log(false == 0);        // true
console.log(false == "");       // true
console.log(0 == "");           // true

console.log('-- 3. !NotExist, !Falsy, and !NaN return true --');
console.log(!undefined);        // true
console.log(!null);             // true

console.log(!false);            // true
console.log(!"");               // true
console.log(!0);                // true

console.log(!NaN);              // true

console.log('-- 4. [] is not falsy, but [] == false because [].toString() returns "" --');
console.log(false == []);       // true
console.log([].toString());     // ""

console.log(![]);               // false

console.log('-- 5. {} is not falsy, and {} != false, because {}.toString() returns "[object Object]" --');
console.log(false == {});       // false
console.log({}.toString());     // [object Object]

console.log(!{});               // false

console.log('-- Comparing --');
console.log('-- 1. string will be converted to number or NaN when comparing with a number, and "" will be converted to 0 --');
console.log(12 < "2");          // false
console.log("12" < "2");        // true
console.log("" < 2);            // true

console.log('-- 2. NaN can not be compared with any value, even if NaN itself, always return false --');
console.log(NaN == NaN);        // false

console.log(NaN == null);       // false
console.log(NaN == undefined);  // false
console.log(0 <= NaN);          // false
console.log(0 >= NaN);          // false
console.log(undefined <= NaN);  // false
console.log(undefined >= NaN);  // false
console.log(null <= NaN);       // false
console.log(null >= NaN);       // false

console.log(2 <= "2a");         // false, since "2a" is converted to NaN
console.log(2 >= "2a");         // false, since "2a" is converted to NaN

console.log('-- 3. undefined can only == null and == undefined, and can not do any other comparing even if <= undefined --');
console.log(undefined == null);         // true
console.log(undefined == undefined);    // true

console.log(undefined == "");           // false
console.log(undefined == false);        // false
console.log(undefined <= undefined);    // false
console.log(undefined <= null);         // false
console.log(undefined >= null);         // false
console.log(0 <= undefined);            // false
console.log(0 >= undefined);            // false

console.log('-- 4. null will be converted to "" when <, >, <=, >= comparing --');
console.log(12 <= null);        // false
console.log(12 >= null);        // true
console.log("12" <= null);      // false
console.log("12" >= null);      // true

console.log(0 == null);         // false
console.log("" == null);        // false

console.log('-- 5. object, including {}, [], will be call toString() when comparing --');
console.log(12 < {});           // false, since {}.toString() is "[object Object]", and then converted to NaN
console.log(12 > {});           // false, since {}.toString() is "[object Object]", and then converted to NaN
console.log("[a" < {});         // true, since {}.toString() is "[object Object]"
console.log("[a" > {});         // false, since {}.toString() is "[object Object]"
console.log(12 < []);           // false, since {}.toString() is "", and then converted to 0
console.log(12 > []);           // true, since {}.toString() is "", and then converted to 0
console.log("[a" < []);         // false, since {}.toString() is ""
console.log("[a" > []);         // true, since {}.toString() is ""

console.log('-- 6. According to 4 and 5, we can get below weird result: --');
console.log(null < []);         // false
console.log(null > []);         // false
console.log(null == []);        // false
console.log(null <= []);        // true
console.log(null >= []);        // true

2

ใน if (arr) มันจะถูกประเมินเสมอ (ToBoolean) เป็นจริงถ้า arr เป็นวัตถุเพราะวัตถุทั้งหมดใน JavaScript นั้นเป็นความจริง (null ไม่ใช่วัตถุ!)

[] == falseได้รับการประเมินในแนวทางแบบวนซ้ำ ในตอนแรกถ้าด้านใดด้านหนึ่ง==เป็นแบบดั้งเดิมและอีกด้านหนึ่งเป็นวัตถุมันจะแปลงวัตถุเป็นแบบดั้งเดิมในตอนแรกจากนั้นแปลงทั้งสองด้านเป็นจำนวนหากทั้งสองฝ่ายไม่ได้ใช้string(การเปรียบเทียบสตริงจะถูกใช้ ดังนั้นการเปรียบเทียบจะซ้ำเหมือน[] == false-> '' == false-> ->0 == 0true


2

ตัวอย่าง:

const array = []
const boolValueOfArray = !!array // true

มันเกิดขึ้นเพราะ

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []เป็นArrayวัตถุเปล่าToPrimitive([])→→ "" ToNumber("")→→0
  2. ToNumber(false) → 0
  3. 0 == 0 →จริง

1

อาเรย์ที่มีองค์ประกอบ (ไม่ว่าจะเป็น 0, เป็นเท็จหรืออาเรย์ที่ว่างเปล่าอื่น) จะสามารถแก้ไขการtrueใช้การเปรียบเทียบความเสมอภาคแบบนามธรรม==ได้เสมอ

1. [] == false; // true, because an empty array has nothing to be truthy about
2. [2] == false; // false because it has at least 1 item
3. [false] == false; // also false because false is still an item
4. [[]] == false; // false, empty array is still an item

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

1. [] === false; // false, because an array (regardless of empty or not) is not strictly comparable to boolean `false`
2. [] === true; // false, same as above, cannot strictly compare [] to boolean `true`
3. [[]] === false; // true, because see #1

-1

คุณสามารถล้างอาร์เรย์ JavaScript โดยการอ้างอิงไปยังอาร์เรย์ใหม่โดยใช้หรือลบองค์ประกอบของอาร์เรย์อ้างอิงในปัจจุบันlist = []list.length = 0

ที่มา: JavaScript Empty Array


-2

ไม่มีข้อใดถูกช่วยให้ฉันเมื่อพยายามใช้ปลั๊กอินการทำแผนที่ knockout.js บางทีอาจเป็นเพราะ "อาร์เรย์ว่าง" ไม่ได้ว่างเปล่าจริงๆ

ฉันสิ้นสุดการใช้: data-bind="if: arr().length"ซึ่งทำเคล็ดลับ

นี่เป็นคำถามที่น่าพิศวงไม่ใช่คำถามของ OP แต่อาจช่วยให้คนอื่นดูที่นี่ในสถานการณ์ที่คล้ายกัน


คำตอบนี้ไม่เกี่ยวข้อง
fauverism

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