Các mảng trống dường như bằng nhau đúng và sai cùng một lúc


201

Mảng trống là đúng nhưng chúng cũng bằng sai.

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??");

Tôi đoán điều này là do chuyển đổi ngầm được vận hành bởi toán tử đẳng thức.

Bất cứ ai có thể giải thích những gì đang xảy ra đằng sau hậu trường?


1
Đây là một chủ đề tương tự sẽ làm sáng tỏ vấn đề: stackoverflow.com/questions/4226101/
mẹo

2
Lưu ý, điều đó arr == truekhông được đánh giá là đúng ;-)
Michael Krelin - hacker

5
Wow ... chỉ khi bạn nghĩ rằng bạn đã có tất cả điều này xuống.
harpo

3
Để tránh sự ép buộc kiểu Javascript WTF, hãy sử dụng opeartor bình đẳng nghiêm ngặt ===. Sau đó, nếu bạn muốn kiểm tra sự trống rỗng của một mảng, hãy sử dụngarr === []
DjebbZ

17
Nếu bạn muốn kiểm tra sự trống rỗng của một mảng thì KHÔNG nên sử dụng arr === [], vì điều đó LUÔN LUÔN trả về false, vì phía bên phải đang tạo một mảng mới và biến ở bên trái không thể tham chiếu đến thứ bạn vừa tạo. Kiểm tra sự trống rỗng nên được thực hiện bằng cách tìm kiếm arr.length === 0.
Kyle Baker

Câu trả lời:


274

Bạn đang thử nghiệm những thứ khác nhau ở đây.

if (arr) được gọi trên đối tượng (Array là thể hiện của Object trong JS) sẽ kiểm tra xem đối tượng có mặt hay không và trả về true / false.

Khi bạn gọi if (arr == false)bạn so sánh các giá trị của đối tượng này và falsegiá trị nguyên thủy . Trong nội bộ, arr.toString()được gọi, trong đó trả về một chuỗi rỗng "".

Điều này là do toStringđược gọi trên trả về Array Array.join()và chuỗi rỗng là một trong những giá trị sai lệch trong JavaScript.


2
Bạn có thể giải thích tại sao Boolean([])trở lại true?
Devy

11
theo quy ước, trong JS nếu các đối tượng bị ép buộc vào Boolean, chúng luôn bị ép buộc thành TRUE. xem bảng "Boolean bối cảnh" tại: javascript.info/tutorial/object-conversion
Niki

2
@Devy tất cả các đối tượng trong JavaScript là trung thực, vì vậy việc chuyển đổi bất kỳ đối tượng nào thành Boolean là đúng. Xem 2ality.com/2013/08/objects-truthy.html
Thomson

62

Về dòng:

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

Có lẽ những điều này sẽ giúp:

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

Những gì tôi tin là đang xảy ra là boolean falsebị ép buộc để 0so sánh với một đối tượng (phía bên trái). Đối tượng bị ép buộc thành một chuỗi (chuỗi trống). Sau đó, chuỗi trống cũng bị ép thành một số, cụ thể là số không. Và do đó, so sánh cuối cùng là 0== 0, đó là true.

Chỉnh sửa: Xem phần này của thông số kỹ thuật để biết chi tiết về chính xác cách thức hoạt động của nó.

Đây là những gì đang xảy ra, bắt đầu từ quy tắc số 1:

1. Nếu Loại (x) khác với Loại (y), hãy đến bước 14.

Quy tắc tiếp theo được áp dụng là # 19:

19. Nếu Loại (y) là Boolean, trả về kết quả so sánh x == ToNumber (y).

Kết quả ToNumber(false)0, vì vậy bây giờ chúng ta có:

[] == 0

Một lần nữa, quy tắc số 1 bảo chúng ta nhảy sang bước # 14, nhưng bước tiếp theo thực sự áp dụng là # 21:

21. Nếu Loại (x) là Đối tượng và Loại (y) là Chuỗi hoặc Số, hãy trả về kết quả so sánh ToPrimitive (x) == y.

Kết quả ToPrimitive([])là chuỗi rỗng, vì vậy bây giờ chúng ta có:

"" == 0

Một lần nữa, quy tắc số 1 bảo chúng ta chuyển sang bước # 14, nhưng bước tiếp theo thực sự áp dụng là # 17:

17. Nếu Loại (x) là Chuỗi và Loại (y) là Số, hãy trả về kết quả so sánh ToNumber (x) == y.

Kết quả ToNumber("")0, để lại cho chúng ta:

0 == 0

Bây giờ, cả hai giá trị có cùng loại, vì vậy các bước tiếp tục từ # 1 đến # 7, cho biết:

7. Nếu x là cùng một giá trị số với y, trả về true.

Vì vậy, chúng tôi trở lại true.

Tóm lại:

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

2
Tài liệu tham khảo tuyệt vời! Để tránh nhầm lẫn, có thể hữu ích khi đề cập rằng lý do "quy tắc tiếp theo được áp dụng là # 19" mặc dù quy tắc số 1 nói "chuyển sang bước 14", là vì các bước 14-18 không khớp với các loại của các giá trị được so sánh.
Sean the Bean

2
Giải thích tốt đẹp. Điều khó hiểu với tôi là mảng trống được coi [] == 0là sự thật , 0 là falsey, và vẫn là sự thật. Tôi hiểu làm thế nào điều này xảy ra dựa trên lời giải thích của bạn về thông số kỹ thuật, nhưng có vẻ như hành vi ngôn ngữ kỳ quặc từ quan điểm logic.
bigh_29

7

Để bổ sung câu trả lời của Wayne và cố gắng giải thích lý do ToPrimitive([])trả về "", nên xem xét hai loại câu trả lời có thể có cho câu hỏi 'tại sao'. Loại câu trả lời đầu tiên là: "bởi vì đặc tả nói rằng đây là cách JavaScript sẽ hoạt động." Trong thông số ES5, phần 9.1 , mô tả kết quả của ToPrimitive làm giá trị mặc định cho Đối tượng:

Giá trị mặc định của một đối tượng được lấy bằng cách gọi phương thức bên trong [[DefaultValue]] của đối tượng, chuyển qua gợi ý tùy chọn PreferredType.

Mục 8.12.8 mô tả [[DefaultValue]]phương pháp. Phương thức này lấy "gợi ý" làm đối số và gợi ý có thể là Chuỗi hoặc Số. Để đơn giản hóa vấn đề bằng cách phân phối với một số chi tiết, nếu gợi ý là Chuỗi, sau đó [[DefaultValue]]trả về giá trị toString()nếu nó tồn tại và trả về giá trị nguyên thủy và trả về giá trị của valueOf(). Nếu gợi ý là Số, các ưu tiên của toString()valueOf()được đảo ngược để valueOf()được gọi đầu tiên và giá trị của nó được trả về nếu đó là số nguyên thủy. Do đó, việc [[DefaultValue]]trả về kết quả của toString()hay valueOf()phụ thuộc vào PreferredType được chỉ định cho đối tượng và liệu các hàm này có trả về giá trị nguyên thủy hay không.

valueOf()Phương thức đối tượng mặc định chỉ trả về chính đối tượng, có nghĩa là trừ khi một lớp ghi đè phương thức mặc định, valueOf()chỉ trả về chính đối tượng đó. Đây là trường hợp cho Array. [].valueOf()trả về []chính đối tượng Vì một Arrayđối tượng không phải là nguyên thủy, nên [[DefaultValue]]gợi ý là không liên quan: giá trị trả về cho một mảng sẽ là giá trị của toString().

Để trích dẫn JavaScript của David Flanagan : Hướng dẫn dứt khoát , nhân tiện, đây là một cuốn sách tuyệt vời nên là nơi đầu tiên của mọi người để có câu trả lời cho các loại câu hỏi này:

Các chi tiết của chuyển đổi đối tượng sang số này giải thích tại sao một mảng trống chuyển đổi thành số 0 và tại sao một mảng có một phần tử duy nhất cũng có thể chuyển đổi thành một số. Mảng kế thừa phương thức valueOf () mặc định trả về một đối tượng chứ không phải giá trị nguyên thủy, do đó, chuyển đổi mảng thành số phụ thuộc vào phương thức toString (). Mảng trống chuyển thành chuỗi rỗng. Và chuỗi rỗng chuyển đổi thành số 0. Một mảng có một phần tử duy nhất chuyển đổi thành cùng một chuỗi mà một phần tử thực hiện. Nếu một mảng chứa một số duy nhất, số đó được chuyển đổi thành một chuỗi và sau đó trở lại một số.

Loại câu trả lời thứ hai cho câu hỏi "tại sao", ngoại trừ "bởi vì thông số kỹ thuật nói", đưa ra một số lời giải thích cho lý do tại sao hành vi có ý nghĩa từ quan điểm thiết kế. Về vấn đề này tôi chỉ có thể suy đoán. Đầu tiên, làm thế nào để chuyển đổi một mảng thành một số? Khả năng hợp lý duy nhất tôi có thể nghĩ đến là chuyển đổi một mảng trống thành 0 và bất kỳ mảng không trống nào thành 1. Nhưng như câu trả lời của Wayne đã tiết lộ, dù sao thì một mảng trống sẽ được chuyển đổi thành 0 cho nhiều loại so sánh. Ngoài ra, thật khó để nghĩ về giá trị trả về nguyên thủy hợp lý cho Array.valueOf (). Vì vậy, người ta có thể lập luận rằng nó có ý nghĩa hơn khi Array.valueOf()được mặc định và trả về chính Array, dẫn toString()đến kết quả được sử dụng bởi ToPrimitive. Nó chỉ có ý nghĩa hơn để chuyển đổi một mảng thành một chuỗi, chứ không phải là một số.

Hơn nữa, như được gợi ý bởi trích dẫn của Flanagan, quyết định thiết kế này không cho phép một số loại hành vi có lợi. Ví dụ:

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

Hành vi này cho phép bạn so sánh một mảng phần tử đơn với các số và nhận được kết quả mong đợi.


Cảm ơn câu trả lời này, đó là lời giải thích khá chi tiết mà câu hỏi còn thiếu.
Bình Estus

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

Trong if (Array), nó luôn được đánh giá (ToBoolean) thành true nếu Array là một đối tượng vì tất cả các đối tượng trong JavaScript là trung thực . (null không phải là một đối tượng!)

[] == falseđược đánh giá theo cách tiếp cận lặp lại. Lúc đầu, nếu một bên ==là nguyên thủy và bên kia là đối tượng, ban đầu nó chuyển đổi đối tượng thành nguyên thủy, sau đó chuyển đổi cả hai bên thành Số nếu cả hai bên đều không string(so sánh chuỗi được sử dụng nếu cả hai bên là chuỗi). Vì vậy, so sánh được lặp đi lặp lại như, [] == false-> '' == false-> 0 == 0-> true.


2

Thí dụ:

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

Nó xảy ra bởi vì

ToNumber(ToPrimitive([])) == ToNumber(false)  
  1. []Arrayđối tượng rỗng → ToPrimitive([])→ "" → ToNumber("")0
  2. ToNumber(false) → 0
  3. 0 == 0 → đúng

1

Một mảng có các phần tử (bất kể là 0, sai hay một mảng trống nào khác), luôn luôn giải quyết truebằng cách sử dụng So sánh đẳng thức trừu tượng ==.

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

Nhưng bằng cách sử dụng So sánh bình đẳng nghiêm ngặt ===, bạn đang cố gắng đánh giá nội dung của biến cũng như kiểu dữ liệu của nó, đó là lý do:

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

Bạn có thể làm trống một mảng JavaScript bằng cách tham chiếu nó đến một mảng mới, sử dụng list = []hoặc xóa các thành phần của mảng hiện được tham chiếu list.length = 0.

Nguồn: Mảng trống JavaScript


-2

Không có điều nào ở trên giúp tôi, khi cố gắng sử dụng plugin ánh xạ knout.js, có lẽ vì một "mảng trống" không thực sự trống.

Tôi đã kết thúc bằng cách sử dụng: data-bind="if: arr().length"mà đã lừa

Điều này đặc biệt đối với loại trực tiếp, không phải câu hỏi của OP, nhưng có lẽ nó sẽ giúp người khác duyệt ở đây trong tình huống tương tự.


Câu trả lời này không liên quan
fauverism

chỉ tiếp tuyến, như từ chối :)
domoarigato
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.