MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

相等比較

翻譯不完整。請協助 翻譯此英文文件

在 ES2015,有四個相等比較方法:

  • 一般相等 (==)
  • 嚴格相等 (===):被用於 Array.prototype.indexOf、 Array.prototype.lastIndexOf 和 case-matching 
  • 零值相等:被用於 %TypedArray% 和 ArrayBuffer constructors,以及 Map 和 Set 運算子,還有將在 ES2016 新增的 String.prototype.includes。
  • 同值相等: 用在除上面提及的所有情況。

JavaScript 提供三種不同的值比較運算操作:

  • 嚴格相等 (或稱 "三等於"、"全等") 使用 ===
  • 一般相等 ("雙等於") 使用 ==
  • 還有 Object.is (ECMAScript 2015 新加入)

要用哪個操作取決於你要哪種類型的比較。

簡單來說,一般相等會將型別一致化後比較;嚴格相等則不會(也就是說若型別不同,就會回傳 fasle);以及 Object.is 會和嚴格相等做同樣的事,但會將 NaN-0 和 +0 獨立處理,因此這三個不會互相等於,而 Object.is(NaN, NaN) 則會會傳 true 。 (用一般相等或嚴格相等比較兩個 NaN 時會回傳 false ,因為 IEEE 754 如此規範。) 切記, 此三者的分別必須考慮原型處理;他們在結構上從不視為相同。對於任何非原型物件 x、y,即使他們有著相同結構,但如果是分隔物件,比較就會是 false。

嚴格相等(===

嚴格相等比較兩個值,而被比較的兩個值都不會轉換成其他型態。如果值有不同型態,就會被視為不相等。此外,如果值有相同型態但不同數字,若值相同,比較會被視為相等。

var num = 0;
var obj = new String("0");
var str = "0";

console.log(num === num); // true
console.log(obj === obj); // true
console.log(str === str); // true

console.log(num === obj); // false
console.log(num === str); // false
console.log(obj === str); // false
console.log(null === undefined); // false
console.log(obj === null); // false
console.log(obj === undefined); // false

嚴格比較適合在絕大多數需要比較的情況下使用。對於所有非數字的值,嚴格比較如同字面:一個值只相等於自己。而數字則使用稍微不同的方式:第一種情況是浮點數 0 同時為正和負,解決部分數學問題時,+0 和 -0 是不同的,但在大部分情況下我們用不到,因此嚴格比較將他們視為相同的。第二種情況是浮點數 NaN,用來表示某些定義不明確的數學問題的解, 例如:負無窮加正無窮,嚴格比較認為 NaN 不等於任何值,包含他本身。( (x !== x)只有在 x 是 NaN 時會是 true。)

一般相等(==)

一般相等會先將比較值轉換成同型別後比較。轉換後(可能有一個或兩個都被轉換),進行的幾乎和嚴格比較(===)一樣。 一般相等會對稱: A == B 等同 B == A ,無論 A 和 B 是什麼。(除了型別轉換的順序)

不同型別的一般相等運作如下表:

  比較值 B
    Undefined Null Number String Boolean Object
比較值 A Undefined true true false false false false
Null true true false false false false
Number false false A === B A === ToNumber(B) A === ToNumber(B) A == ToPrimitive(B)
String false false ToNumber(A) === B A === B ToNumber(A) === ToNumber(B) A == ToPrimitive(B)
Boolean false false ToNumber(A) === B ToNumber(A) === ToNumber(B) A === B ToNumber(A) == ToPrimitive(B)
Object false false ToPrimitive(A) == B ToPrimitive(A) == B ToPrimitive(A) == ToNumber(B) A === B

根據上表, ToNumber(A) 嘗試在比較前轉換成一個數字。 這等同 +A (單個 + 運算子)。ToPrimitive(A) 嘗試從物件轉換成原生值,透過嘗試對 A 使用 A.toString 和 A.valueOf 方法。

一般來說,根據 ECMAScript 規範,所有物件應該不等於 undefined 和 null。但大多數瀏覽器允許很小部分的物件(尤其是所有頁面的 document.all 物件)在某些情況下模擬 成 undefined。一般相等是其中一種:當 A 是個被模擬 成 undefined 的物件null == A 且 undefined == A 會是 true。而在其他情況下物件不會等同於 undefined 或 null。

var num = 0;
var obj = new String("0");
var str = "0";

console.log(num == num); // true
console.log(obj == obj); // true
console.log(str == str); // true

console.log(num == obj); // true
console.log(num == str); // true
console.log(obj == str); // true
console.log(null == undefined); // true

// 除了少數情況,這兩個應該是 false。
console.log(obj == null);
console.log(obj == undefined);

部分開發者認為最好別用一般相等。嚴格比較更容易預測,且因為不必轉型,因此更快。

同值相等

同值相等解決了最後一個情況:比較兩個值是否功能相同 。(這裡用了 Liskov 替換原則(英) 為例)當試圖修改一個不可變的屬性:

// 新增一個不可變 NEGATIVE_ZERO 屬性到 Number 原型。
Object.defineProperty(Number, "NEGATIVE_ZERO",
                      { value: -0, writable: false, configurable: false, enumerable: false });

function attemptMutation(v)
{
  Object.defineProperty(Number, "NEGATIVE_ZERO", { value: v });
}

當嘗試修改一個不可變屬性且真的修改時, Object.defineProperty 會出現例外,但若沒有真的要求修改,就沒事。如果 v-0,就不會有修改,也就不會有錯誤出現。但若 v 是 +0Number.NEGATIVE_ZERO 不再擁有自己的不可變屬性。在內部,當一個不可變屬性被重新定義,新的值會用同值相等和原值比較。

Object.is 方法提供同值相等比較。

零值相等

和同值相等一樣,但將 +0-0 視為相同。

一般相等、嚴格相等和同值相等的規範

在 ES5,一般相等 == 在 Section 11.9.3, The Abstract Equality Algorithm 中規範。嚴格相等 === 在 11.9.6, The Strict Equality Algorithm。(可以看看,這很簡短且可讀。註:先讀嚴格相等。)ES5 也在 Section 9.12, The SameValue Algorithm 規範 JS 引擎的行為。他幾乎和嚴格相等一樣,除了 11.9.6.4 和 9.12.4 在處理 Number 時的不同。ES2015 簡短的提出了 Object.is

我們可以發現在 11.9.6.1 中,除了 11.9.6.1 規範型別檢查,嚴格相等規範是從屬於一般相等規範,因為 11.9.6.2–7 和 11.9.3.1.a–f相應。

理解相等比較模型

Prior to ES2015, you might have said of double equals and triple equals that one is an "enhanced" version of the other. For example, someone might say that double equals is an extended version of triple equals, because the former does everything that the latter does, but with type conversion on its operands. E.g., 6 == "6". (Alternatively, someone might say that double equals is the baseline, and triple equals is an enhanced version, because it requires the two operands to be the same type, so it adds an extra constraint. Which one is the better model for understanding depends on how you choose to view things.)

However, this way of thinking about the built-in sameness operators is not a model that can be stretched to allow a place for ES2015's Object.is on this "spectrum". Object.is isn't simply "looser" than double equals or "stricter" than triple equals, nor does it fit somewhere in between (i.e., being both stricter than double equals, but looser than triple equals). We can see from the sameness comparisons table below that this is due to the way that Object.is handles NaN. Notice that if Object.is(NaN, NaN) evaluated to false, we could say that it fits on the loose/strict spectrum as an even stricter form of triple equals, one that distinguishes between -0 and +0. The NaN handling means this is untrue, however. Unfortunately, Object.is simply has to be thought of in terms of its specific characteristics, rather than its looseness or strictness with regard to the equality operators.

Sameness Comparisons
x y == === Object.is
undefined undefined true true true
null null true true true
true true true true true
false false true true true
"foo" "foo" true true true
{ foo: "bar" } x true true true
0 0 true true true
+0 -0 true true false
0 false true false false
"" false true false false
"" 0 true false false
"0" 0 true false false
"17" 17 true false false
[1,2] "1,2" true false false
new String("foo") "foo" true false false
null undefined true false false
null false false false false
undefined false false false false
{ foo: "bar" } { foo: "bar" } false false false
new String("foo") new String("foo") false false false
0 null false false false
0 NaN false false false
"foo" NaN false false false
NaN NaN false false true

When to use Object.is versus triple equals

Aside from the way it treats NaN, generally, the only time Object.is's special behavior towards zeros is likely to be of interest is in the pursuit of certain meta-programming schemes, especially regarding property descriptors when it is desirable for your work to mirror some of the characteristics of Object.defineProperty. If your use case does not require this, it is suggested to avoid Object.is and use === instead. Even if your requirements involve having comparisons between two NaN values evaluate to true, generally it is easier to special-case the NaN checks (using the isNaN method available from previous versions of ECMAScript) than it is to work out how surrounding computations might affect the sign of any zeros you encounter in your comparison.

Here's an in-exhaustive list of built-in methods and operators that might cause a distinction between -0 and +0 to manifest itself in your code:

- (unary negation)

It's obvious that negating 0 produces -0. But the abstraction of an expression can cause -0 to creep in when you don't realize it. For example, consider:

let stoppingForce = obj.mass * -obj.velocity

If obj.velocity is 0 (or computes to 0), a -0 is introduced at that place and propogates out into stoppingForce.

Math.atan2
Math.ceil
Math.pow
Math.round
It's possible for a -0 to be introduced into an expression as a return value of these methods in some cases, even when no -0 exists as one of the parameters. E.g., using Math.pow to raise -Infinity to the power of any negative, odd exponent evaluates to -0. Refer to the documentation for the individual methods.
Math.floor
Math.max
Math.min
Math.sin
Math.sqrt
Math.tan
It's possible to get a -0 return value out of these methods in some cases where a -0 exists as one of the parameters. E.g., Math.min(-0, +0) evalutes to -0. Refer to the documentation for the individual methods.
~
<<
>>
Each of these operators uses the ToInt32 algorithm internally. Since there is only one representation for 0 in the internal 32-bit integer type, -0 will not survive a round trip after an inverse operation. E.g., both Object.is(~~(-0), -0) and Object.is(-0 << 2 >> 2, -0) evaluate to false.

Relying on Object.is when the signedness of zeros is not taken into account can be hazardous. Of course, when the intent is to distinguish between -0 and +0, it does exactly what's desired.

See also

文件標籤與貢獻者

 此頁面的貢獻者: pa-da
 最近更新: pa-da,