空数组似乎同时等于 true 和 false

IT技术 javascript
2021-02-05 05:23:12

空数组为真,但它们也等于假。

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

我猜这是由于相等运算符操作的隐式转换。

谁能解释一下幕后发生了什么?

6个回答

你在这里测试不同的东西。

if (arr) 在对象上调用(Array 是 JS 中 Object 的实例)将检查对象是否存在,并返回 true/false。

当你打电话if (arr == false)你比较这个对象和原始的falsevalue。在内部,arr.toString()被调用,它返回一个空字符串""

这是因为toString在 Array 上调用返回Array.join(),而空字符串是 JavaScript 中的假值之一。

@Devy JavaScript 中的所有对象都是真值,因此将任何对象转换为布尔值是真值。2ality.com/2013/08/objects-truthy.html
2021-03-13 05:23:12
按照惯例,在 JS 中,如果对象被强制为布尔值,它们总是被强制为 TRUE。查看“布尔上下文”表:javascript.info/tutorial/object-conversion
2021-03-16 05:23:12
你能解释为什么Boolean([])返回true吗?
2021-03-27 05:23:12

关于线路:

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

也许这些会有所帮助:

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

我相信正在发生的是布尔值false被强制0用于与对象(左侧)进行比较。该对象被强制转换为一个字符串(空字符串)。然后,空字符串也被强制为一个数字,即零。所以最后的比较是0== 0,也就是true.

编辑:有关具体如何工作的详细信息,请参阅规范的这一部分

下面是正在发生的事情,从规则 #1 开始:

1. 如果 Type(x) 与 Type(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。

所以,我们返回true

简单来说:

ToNumber(ToPrimitive([])) == ToNumber(false)
很好的参考!为避免混淆,尽管规则 #1 说“转到第 14 步”,但提及“适用的下一条规则是 #19”的原因可能会有所帮助,因为第 14-18 步与被比较的值。
2021-03-14 05:23:12
很好的解释。让我感到困惑的是,空数组被认为是真的,0 是假的,但仍然[] == 0是真的。根据您对规范的解释,我了解这是如何发生的,但从逻辑的角度来看,这似乎是奇怪的语言行为。
2021-03-30 05:23:12

为了补充韦恩的回答并试图解释为什么ToPrimitive([])返回"",值得考虑“为什么”问题的两种可能的答案。第一种回答是:“因为规范说这就是 JavaScript 的行为方式。” 在 ES5 规范的9.1 节中,将 ToPrimitive 的结果描述为对象的默认值:

通过调用对象的 [[DefaultValue]] 内部方法,传递可选的提示 PreferredType 来检索对象的默认值。

第 8.12.8 节描述了该[[DefaultValue]]方法。此方法以“提示”作为参数,提示可以是字符串或数字。为了简化事情,省略一些细节,如果提示是字符串,则[[DefaultValue]]返回 的值,toString()如果存在,则返回原始值,否则返回 的值valueOf()如果提示是数字,则toString()的优先级valueOf()颠倒,因此valueOf()首先调用它,如果它是原始类型则返回其值。因此,是否[[DefaultValue]]返回的结果toString()valueOf()取决于指定PreferredType为对象,以及是否这些函数返回原始值。

默认的valueOf()Object 方法只返回对象本身,这意味着除非类覆盖默认方法,否则valueOf()只返回 Object 本身。对于Array. [].valueOf()返回对象[]本身。由于Array对象不是原始对象,因此[[DefaultValue]]提示无关紧要:数组的返回值将是 的值toString()

引用David Flanagan 的JavaScript: The Definitive Guide,顺便说一下,这是一本很棒的书,应该是每个人获得这些类型问题答案的第一个地方:

这种对象到数字转换的细节解释了为什么空数组会转换为数字 0 以及为什么具有单个元素的数组也可以转换为数字。数组继承了默认的 valueOf() 方法,该方法返回一个对象而不是原始值,因此数组到数字的转换依赖于 toString() 方法。空数组转换为空字符串。空字符串将转换为数字 0。具有单个元素的数组将转换为与该元素相同的字符串。如果数组包含单个数字,则该数字将转换为字符串,然后再转换为数字。

对“为什么”问题的第二种回答,除了“因为规范说”之外,从设计的角度解释了为什么这种行为是有意义的。在这个问题上,我只能推测。首先,如何将数组转换为数字?我能想到的唯一合理的可能性是将空数组转换为 0,将任何非空数组转换为 1。但正如韦恩的回答所揭示的那样,无论如何,对于许多类型的比较,空数组都会被转换为 0。除此之外,很难为 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

此行为允许您将单元素数组与数字进行比较并获得预期结果。

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

在 if (arr) 中,如果 arr 是一个对象,它总是被评估 (ToBoolean) 为 true ,因为JavaScript 中的所有对象都是 true(null 不是对象!)

[] == false以迭代方法进行评估。首先,如果一边==是primitive,一边是object,先把object转换成primitive,如果两边都不是,就把两边都转换成Number(如果两边都是string字符串,就用字符串比较)。所以比较是迭代的,[] == false-> '' == false-> 0 == 0-> true