我们都知道JavaScript有==
(强制相等)和===
(严格相等)运算符进行比较。但你可能不知道它们两个究竟有什么不同,并且更重要的是,在 js 引擎中使用它们的时候发生了什么?
前面我们提到 ==
是强制比较。强制意味着 VM 试图将进行比较的双方强制为相同的类型,然后查看它们是否相等。以下我们列举了一些自动被强制相等的例子:
"1" == 1 // true
1 == "1" // true
true == 1 // true
1 == true // true
[1] == 1 // true
1 == [1] // true
你要知道,强制是对称的,如果
a == b
为真,那么b == a
也为真。另一方面,只有当两个操作数完全相同时===
才为真(除了Number.NaN
)。因此,上面的例子都真实的情况下都是假真
(即,在===
的情况下是false
的)。
为什么强制相等有这样的问题,这要归咎与强制相等的规则。
强制相等的规则
实际的规则很复杂(这也是不使用==
的原因)。但是为了显示规则有多么复杂,我通过使用===
实现了==
,带大家看看强制相等的规则到底多复杂:
function doubleEqual(a, b) {if (typeof a === typeof b) return a === b;if (wantsCoercion(a) && isCoercable(b)) {b = b.valueOf();} else if (wantsCoercion(b) && isCoercable(a)) {const temp = a.valueOf();a = b;b = temp;}if (a === b) return true;switch (typeof a) {case "string":if (b === true) return a === "1" || a === 1;if (b === false) return a === "0" || a === 0 || a == "";if (a === "" && b === 0) return true;return a === String(b);case "boolean":if (a === true) return b === 1 || String(b) === "1";else return b === false || String(b) === "0" || String(b) === "";case "number":if (a === 0 && b === false) return true;if (a === 1 && b === true) return true;return a === Number(String(b));case "undefined":return b === undefined || b === null;case "object":if (a === null) return b === null || b === undefined;default:return false;}
}function wantsCoercion(value) {const type = typeof value;return type === "string" || type === "number" || type === "boolean";
}function isCoercable(value) {return value !== null && typeof value == "object";
}
这是不是太复杂了,我甚至不确定这是正确的! 也许有你知道更简单的算法。
但有趣的是,你会发现在上面的算法中,如果其中一个操作数是对象,VM 将调用. valueof()
来允许对象将自身强制转换为基本类型。
强制转换的成本
上面的实现很复杂。那么==
比===
要多浪费多少性能呢? 看看下面这张图,我用基准测试做了一个对比:
其中,图表中越高表示越快(即,每秒操作次数越多)。
首先我们来讨论数字数组。当 VM 注意到数组是纯整数时,它将它们存储在一个称为PACKED_SMI_ELEMENTS
的特殊数组中。在这种情况下,VM 知道将 ==
处理为 ===
是安全的,性能是相同的。这解释了为什么在数字的情况下,==
和 ===
之间没有区别。但是,一旦数组中包含了数字以外的内容,==
的情况就变得很糟糕了。
对于字符串,==
比 ===
的性能下降了 50%,看起来挺糟的是吧。
字符串在VM中是特殊的,但一旦我们涉及到对象,我们就慢了 4 倍。看看 mix
这栏,现在速度减慢了 4 倍!
但还有更糟的。对象可以定义 valueOf
,这样在转换的时候可以将自己强制转换为原语。虽然在对象上定位属性可以通过内联缓存,内联缓存让属性读取变得快速,但在超大容量读取的情况下可能会经历 60 倍的减速,这可能会使情况更糟。如图中最坏情况(objectsMega
)场景所示,==
比===
慢15 倍!
有其他使用 ==
的理由吗
现在,===
非常快! 因此,即使是使用 ===
的15倍减速,在大多数应用程序中也不会有太大区别。尽管如此,我还是很难想出为什么要使用 ==
而不是 ===
的任何理由。强制规则很复杂,而且它存在一个性能瓶颈,所以在使用 ==
之前请三思。