空数组为真,但它们也等于假。
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??");
我猜这是由于相等运算符操作的隐式转换。
谁能解释一下幕后发生了什么?
空数组为真,但它们也等于假。
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??");
我猜这是由于相等运算符操作的隐式转换。
谁能解释一下幕后发生了什么?
你在这里测试不同的东西。
if (arr)
在对象上调用(Array 是 JS 中 Object 的实例)将检查对象是否存在,并返回 true/false。
当你打电话if (arr == false)
你比较值这个对象和原始的false
value。在内部,arr.toString()
被调用,它返回一个空字符串""
。
这是因为toString
在 Array 上调用返回Array.join()
,而空字符串是 JavaScript 中的假值之一。
关于线路:
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)
为了补充韦恩的回答并试图解释为什么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
。