==怎么会比===慢15倍呢?

我们都知道JavaScript有==(相等)和===(严格相等)运算符进行比较。但究竟有何区别,更重要的是,在幕后发生了什么?让我们深入了解!

区别

== 是一种强制转换比较。这里的强制转换意味着虚拟机(VM)试图强制两侧变成相同的类型,然后再比较它们是否相等。以下是一些被自动强制转换成相等的示例:

1
2
3
4
5
6
"1" == 1 // true
1 == "1" // true
true == 1 // true
1 == true // true
[1] == 1 // true
1 == [1] // true

强制转换是对称的,如果 a == btrue,则b == a也是 true。另一方面,=== 只有在两个操作数完全相同(除了 Number.NaN 之外)时才为 true。因此,以上示例中的任何一个都不会在 === 中为 true

强制转换规则

实际规则很复杂(这本身就是不使用 == 的原因之一)。但为了展示规则有多复杂,我尝试使用 === 实现了 ==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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";
}

强制转换的性能成本

哇,这太复杂了,我甚至不确定它是否正确!

有趣的是,如果操作数之一是对象,虚拟机会调用.valueOf()来允许对象强制转换为原始类型。

好吧,那个实现很复杂。那么 =====慢多少?看看这个图表。 (查看此处的基准测试)

首先让我们谈谈数字数组。当虚拟机注意到数组是纯整数时,它会将它们存储在一个称为 PACKED_SMI_ELEMENTS 的特殊数组中。在这种情况下,虚拟机知道可以将 == 视为 ===,性能是一样的。这解释了为什么在数字的情况下===== 没有区别。但一旦数组包含除了数字以外的东西,对于 == 来说情况就开始变得糟糕了。

对于字符串,== 的性能降低了 50%,而且只会变得更糟。

字符串在虚拟机中是特殊的,但一旦涉及到对象,我们就会慢 4 倍。看看混合列,减速现在是 4 倍!

但情况变得更糟。对象可以定义 valueOf 来将自身强制转换为原始值。所以现在虚拟机必须调用该方法。当然,定位对象上的属性是受内联缓存控制的。内联缓存是属性读取的快速路径,但是一个多态读取可能会体验到 60 倍的减速,这可能会使情况变得更糟。如图所示,作为最坏情况(objectsMega)的情况下,===== 慢 15 倍!

继续使用 == 的理由?

现在,===非常快!因此,即使使用==将速度降低15倍,在大多数应用程序中也不会产生太大的影响。然而,我很难想出为什么应该使用==而不是===的任何理由。强制转换规则很复杂,而且它是性能上的一大障碍,所以在使用==之前请三思。