Skip to content

Commit

Permalink
微调
Browse files Browse the repository at this point in the history
  • Loading branch information
familyboat committed Oct 22, 2024
1 parent 2fd1b1f commit 191ffb6
Showing 1 changed file with 17 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
title: JavaScript 中的相等性判断
slug: Web/JavaScript/Equality_comparisons_and_sameness
l10n:
sourceCommit: 8e1184924387f88e2ee63a3c786b007aaf573105
---

{{jsSidebar("Intermediate")}}
Expand All @@ -11,24 +13,24 @@ JavaScript 提供三种不同的值比较运算:
- [`==`](/zh-CN/docs/Web/JavaScript/Reference/Operators/Equality)——宽松相等(两个等号)
- [`Object.is()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/is)

选择哪个运算取决于你需要什么样的比较。简单来说:
选择哪种运算取决于你需要什么样的比较。简单来说:

- 在比较两个操作数时,双等号(`==`)将执行类型转换,并且会按照 IEEE 754 标准对 `NaN``-0``+0` 进行特殊处理(故 `NaN != NaN`,且 `-0 == +0`);
- 三等号(`===`)做的比较与双等号相同(包括对 `NaN``-0``+0` 的特殊处理)但不进行类型转换;如果类型不同,则返回 `false`
- `Object.is()` 既不进行类型转换,也不对 `NaN``-0``+0` 进行特殊处理(这使它和 `===` 在除了那些特殊数字值之外的情况具有相同的表现)。

上述三个操作分别与 JavaScript 四个相等算法中的三个相对应:

- [IsLooselyEqual](https://tc39.es/ecma262/#sec-islooselyequal)`==`
- [IsStrictlyEqual](https://tc39.es/ecma262/#sec-isstrictlyequal)`===`
- [SameValue](https://tc39.es/ecma262/#sec-samevalue)`Object.is()`
- [SameValueZero](https://tc39.es/ecma262/#sec-samevaluezero):被许多内置运算使用
- [IsLooselyEqual](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal)`==`
- [IsStrictlyEqual](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal)`===`
- [SameValue](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevalue)`Object.is()`
- [SameValueZero](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluezero):被许多内置运算使用

请注意,这些算法的区别都与它们对原始类型值的处理有关;它们都不会比较参数是否具有理论上相似的结构。对于任何具有相同的结构,但不是同一对象本身的非原始类型对象 `x``y` ,上述所有形式都将计算为 `false`
请注意,这些算法的区别都与它们对原始值的处理有关;它们都不会比较参数是否具有概念上相似的结构。对于任何具有相同的结构、但不是同一对象的非原始类型对象 `x``y` ,上述所有形式都将计算为 `false`

## 使用 === 进行严格相等比较

严格相等比较两个值是否相等。两个被比较的值在比较前都不进行隐式转换。如果两个被比较的值具有不同的类型,这两个值是不相等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是 number 类型时,两个值相等。最后,如果两个值都是 number 类型,当两个都不是 `NaN`,并且数值相同,或是两个值分别为 `+0``-0` 时,两个值被认为是相等的。
严格相等比较两个值是否相等。两个被比较的值在比较前都不进行隐式转换。如果两个被比较的值具有不同的类型,则认为这两个值是不相等的。如果两个被比较的值类型相同,值也相同,并且都不是 number 类型时,则认为这两个值是相等。最后,如果两个值都是 number 类型,当两个都不是 `NaN`,并且数值相同,或是两个值分别为 `+0``-0` 时,两个值被认为是相等的。

```js
const num = 0;
Expand All @@ -47,7 +49,7 @@ console.log(obj === null); // false
console.log(obj === undefined); // false
```

在日常中使用严格相等几乎总是正确的选择。对于除了数值之外的值,严格相等使用明确的语义进行比较:一个值只与自身严格相等。对于数值,严格相等使用略加修改的语义来处理两个特殊情况:第一个情况是,浮点数 0 是不分正负的。区分 `+0``-0` 在解决一些特定的数学问题时是必要的,但是大部分情况下我们并不用关心。严格相等认为这两个值是全等的。第二个情况是,浮点数包含了 `NaN` 值,用来表示某些定义不明确的数学问题的解,例如:正无穷加负无穷。严格相等认为 `NaN` 与其他任何值都不全等,包括它自己。(等式 `(x !== x)` 成立的唯一情况是 `x` 的值为 `NaN`
使用严格相等几乎总是正确的选择。对于除了数字之外的值,严格相等有着明确的语义:值仅与它自身相等。对于数字,严格相等有着稍微不同的语义处理两个特殊情况:第一个情况是,浮点数 0 是不分正负的。区分 `+0``-0` 在解决一些特定的数学问题时区分正负是必要的,但是大部分情况下我们并不用关心。严格相等认为这两个值是全等的。第二个情况是,浮点数包含了 `NaN` 值,用来表示某些定义不明确的数学问题的解,例如:正无穷加负无穷。严格相等认为 `NaN` 与其他任何值都不全等,包括它自己。(等式 `(x !== x)` 成立的唯一情况是 `x` 的值为 `NaN`

除了 `===` 之外,数组索引查找方法也使用严格相等,包括 [`Array.prototype.indexOf()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)[`Array.prototype.lastIndexOf()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf)[`TypedArray.prototype.index()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/indexOf)[`TypedArray.prototype.lastIndexOf()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/lastIndexOf)[`case`](/zh-CN/docs/Web/JavaScript/Reference/Statements/switch) 匹配。这意味着你不能使用 `indexOf(NaN)` 查找数组中 `NaN` 值的索引,也不能将 `NaN` 用作 `case` 值在 `switch` 语句中匹配任何内容。

Expand All @@ -61,7 +63,7 @@ switch (NaN) {

## 使用 == 进行宽松相等比较

宽松相等是*对称的*:对于任何 `A``B` 的值,`A == B` 总是与 `B == A` 具有相同的语义(除了转换应用的顺序)。使用 `==` 执行宽松相等的行为如下:
宽松相等是*对称的*:对于任何 `A``B` 的值,`A == B` 总是与 `B == A` 具有相同的语义(除了转换的应用顺序)。使用 `==` 执行宽松相等的行为如下:

1. 如果操作数具有相同的类型,则按如下方式进行比较:
- 对象(Object):仅当两个操作数引用同一个对象时返回 `true`
Expand All @@ -76,11 +78,11 @@ switch (NaN) {
- 如果是相同的类型,使用步骤 1 进行比较。
- 如果其中一个操作数是符号而另一个不是,返回 `false`
- 如果其中一个操作数是布尔值而另一个不是,则[将布尔值转换为数字](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number#number_强制转换)`true` 转换为 1,`false` 转换为 0。然后再次对两个操作数进行宽松比较。
- 数字与字符串:[将字符串转换为数字](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number#number_强制转换)转换失败将导致 `NaN`,这将保证相等比较为 `false`
- 数字与大整型:按数值进行比较。如果数字的值为 ±∞`NaN`,返回 `false`
- 数字与字符串:[将字符串转换为数字](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number#number_强制转换)转换失败的结果为 `NaN`,这将保证相等比较为 `false`
- 数字与大整型:按数字的值进行比较。如果数字为 ±Infinity`NaN`,返回 `false`
- 字符串与大整型:使用与 [`BigInt()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt) 构造函数相同的算法将字符串转换为大整型数。如果转换失败,返回 `false`

一般而言,根据 ECMAScript 规范,所有原始类型和对象都不与 `undefined``null` 宽松相等。但是大部分浏览器允许非常有限的一类对象(即,所有页面中的 `document.all` 对象)在某些情况下表现出*模拟* `undefined` 值特性。宽松相等就是这些情况之一:当且仅当 A 是一个*模拟* `undefined` 的对象时,`null == A``undefined == A` 将会计算得到 `true`。在其他所有情况下,一个对象都不会与 `undefined``null` 宽松相等。
一直以来,根据 ECMAScript 规范,所有原始值和对象都不与 `undefined``null` 宽松相等。但是大部分浏览器允许非常有限的一类对象(即,所有页面中的 `document.all` 对象)在某些情况下表现出*模拟* `undefined` 值特性。宽松相等就是这些情况之一:当且仅当 A 是一个*模拟* `undefined` 的对象时,`null == A``undefined == A` 将会计算得到 `true`。在其他所有情况下,一个对象都不会与 `undefined``null` 宽松相等。

在大多数情况下,不建议使用宽松相等。使用严格相等进行比较的结果更容易预测,并且由于缺少类型强制转换可以更快地执行。

Expand Down Expand Up @@ -141,13 +143,13 @@ function sameValueZero(x, y) {
}
```

零值相等与严格相等的区别在于其将 `NaN` 视作是相等的,与同值相等的区别在于其将 `-0``0` 视作相等的。这使得它在搜索期间通常具有最实用的行为,特别是在与 `NaN` 一起使用时。它被用于 [`Array.prototype.includes()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)[`TypedArray.prototype.includes()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/includes)[`Map`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map)[`Set`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set) 方法用来比较键的相等性
零值相等与严格相等的区别在于其将 `NaN` 视作是相等的,与同值相等的区别在于其将 `-0``0` 视作相等的。这使得它在搜索期间通常具有最实用的行为,特别是在处理 `NaN` 。它被用于 [`Array.prototype.includes()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)[`TypedArray.prototype.includes()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/includes)[`Map`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map)[`Set`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Set) 需要比较键的相等性的方法中

## 相等性方法比较

在比较双等号和三等号时,人们通常说一个是另一个的“增强”版本。例如,双等号可以被称为三等号的扩展版本,因为前者可以执行后者的所有操作,但是会对其操作数进行类型转换——例如 `6 == "6"`。或者,也可以说双等号是基础,而三等号是增强版本,因为它要求两个操作数是相同的类型,因此增加了额外的约束。

然而,这种思维方式意味着相等比较形成了一个一维的“光谱”,其中“完全严格”位于一端,“完全宽松”位于另一端。这个模型在 {{jsxref("Object.is")}} 方面存在缺陷,因为它既不比双等号“宽松”,也不比三等号“严格”,也不处于两者之间(可以说既比双等号严格,又比三等号宽松)。从下面的相同比较表中,我们可以看出这是由于 {{jsxref("Object.is")}} 处理 {{jsxref("NaN")}} 的方式。请注意,如果 `Object.is(NaN, NaN)` 求值得到 `false`,我们*可以*说它适合宽松/严格光谱,作为三等号的更严格形式,可以区分 `-0``+0` 。然而,{{jsxref("NaN")}} 的处理意味着这是不正确的。不幸的是,{{jsxref("Object.is")}} 必须根据其特定特征来考虑,而不是根据其相等运算符的宽松度或严格度来考虑。
然而,这种思维方式意味着相等比较形成了一个一维的“光谱”,其中“完全严格”位于一端,“完全宽松”位于另一端。这个模型对 {{jsxref("Object.is")}} 而言存在缺陷,因为它既不比双等号“宽松”,也不比三等号“严格”,也不处于两者之间(可以说既比双等号严格,又比三等号宽松)。从下面的相同比较表中,我们可以看出这是由于 {{jsxref("Object.is")}} 处理 {{jsxref("NaN")}} 的方式。请注意,如果 `Object.is(NaN, NaN)` 求值得到 `false`,我们*可以*说它适合宽松/严格光谱,作为三等号的更严格形式,可以区分 `-0``+0` 。然而,{{jsxref("NaN")}} 的处理意味着这是不正确的。不幸的是,{{jsxref("Object.is")}} 必须根据其特定特征来考虑,而不是根据其相等运算符的宽松度或严格度来考虑。

| x | y | `==` | `===` | `Object.is` | `SameValueZero` |
| ------------------- | ------------------- | ---------- | ---------- | ----------- | --------------- |
Expand Down Expand Up @@ -186,7 +188,7 @@ function sameValueZero(x, y) {

- [`-`(一元减)](/zh-CN/docs/Web/JavaScript/Reference/Operators/Unary_negation)

- : 考虑以下例子
- : 注意下面的示例

```js
const stoppingForce = obj.mass * -obj.velocity;
Expand Down

0 comments on commit 191ffb6

Please sign in to comment.