天天看點

JavaScript—相等性判斷

JavaScript 提供三種不同的比較操作符:

  • 嚴格相等,使用 ===
  • (非嚴格)相等,使用 ==
  • 以及 

    Object.is

     (ECMAScript 6 新特性)

ES2015标準提供以下四種比較操作符:

  • (非嚴格)相等 (

    ==

    )
  • 嚴格相等 (

    ===

    ): 用于

    Array.prototype.indexOf

    Array.prototype.lastIndexOf

    , 以及 

    case

    語句的比對操作
  • 零值相等: 用于

    TypedArray

    ArrayBuffer

    的構造、

    Map

    Set

    操作, 并将用于ES2016标準中的

    String.prototype.includes

  • 同值相等: 用于所有其他場景

你可以根據你的需要選擇操作符。

簡單地說,兩等号判等會在比較時進行類型轉換;三等号判等不會進行類型轉換(如果類型不同會直接傳回 false ); 

Object.is

 在三等号判等的基礎上特别處理了 

NaN

 、 

-0

 和 

+0

 ,保證 -0 和 +0 不再相同,但 

Object.is(NaN, NaN)

 會傳回 

true

。(像其他數值一樣比較 NaN ——由于 IEEE 754 的規範,無論使用雙等号或三等号,比較 NaN 都會得到 false )但請注意,此外,這三個運算符的原語中,沒有一個會比較兩個變量是否結構上概念類似。對于任意兩個不同的非原始對象,即便他們有相同的結構, 以上三個運算符都會計算得到 false 。

嚴格相等 

===

EDIT

全等操作符比較兩個值是否相等,兩個被比較的值在比較前都不進行隐式轉換。如果兩個被比較的值具有不同的類型,這兩個值是不全等的。否則,如果兩個被比較的值類型相同,值也相同,并且都不是 number 類型時,兩個值全等。最後,如果兩個值都是 number 類型,當兩個都不是 NaN,并且數值相同,或是兩個值分别為 +0 和 -0 時,兩個值被認為是全等的。

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

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)

非嚴格相等 

==

EDIT

相等操作符比較兩個值是否相等,在比較前将兩個被比較的值轉換為相同類型。在轉換後(等式的一邊或兩邊都可能被轉換),最終的比較方式等同于全等操作符 === 的比較方式。 相等操作符滿足交換律。

相等操作符對于不同類型的值,進行的比較如下圖所示:

被比較值 B
Undefined Null Number String Boolean Object
被比較值 A Undefined

true

true

false

false

false

IsFalsy(B)

Null

true

true

false

false

false

IsFalsy(B)

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)

ToPrimitive(B) == A

Boolean

false

false

ToNumber(A) === B

ToNumber(A) === ToNumber(B)

A === B

false

Object false false

ToPrimitive(A) == B

ToPrimitive(A) == B

ToPrimitive(A) == ToNumber(B)

A === B

在上面的表格中,

ToNumber(A)

 嘗試在比較前将參數 A 轉換為數字,這與 +A(單目運算符+)的效果相同。通過嘗試依次調用 A 的A.toString 和 A.valueOf 方法,将參數 A 轉換為原始值(Primitive)。

一般而言,根據 ECMAScript 規範,所有的對象都與 

undefined 

和 

null 

不相等。但是大部分浏覽器允許非常窄的一類對象(即,所有頁面中的 

document.all 

對象),在某些情況下,充當效仿 

undefined 

的角色。相等操作符就是在這樣的一個背景下。是以,

IsFalsy(A) 

方法的值為 

true 

,當且僅當 

效仿 

undefined

。在其他所有情況下,一個對象都不會等于 

undefined 

或 

null

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

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

// both false, except in rare cases
console.log(obj == null);
console.log(obj == undefined);
           

有些開發者認為,最好永遠都不要使用相等操作符。全等操作符的結果更容易預測,并且因為沒有隐式轉換,全等比較的操作會更快。

同值相等EDIT

同值相等解決了最後一個用例:确定兩個值是否在任何情況下功能上是相同的。(這個用例示範了裡氏替換原則的執行個體。)當試圖對不可變(immutable)屬性修改時發生出現的情況:

// 向 Nmuber 構造函數添加一個不可變的屬性 NEGATIVE_ZERO
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 是 +0 ,則會抛出異常。不可變屬性和新設定的值使用 same-value 相等比較。

同值相等由 

Object.is

 方法提供。

零值相等EDIT

與同值相等類似,不過會認為 +0 與 -0 相等。

規範中的相等、嚴格相等以及同值相等EDIT

在 ES5 中, 

==

 相等在 Section 11.9.3, The Abstract Equality Algorithm; 

===

 相等在 11.9.6, The Strict Equality Algorithm。(請參考這兩個連結,他們很簡潔易懂。提示:請先閱讀嚴格相等的算法)ES5 也提供了 same-value 相等, Section 9.12, The SameValue Algorithm ,用在 JS 引擎内部。除了 11.9.6.4 和 9.12.4 在處理數字上的不同外,它基本和嚴格相等算法相同。ES6 簡單地通過  

Object.is

 暴露了這個算法。

我們可以看到,使用雙等或三等時,除了 11.9.6.1 類型檢查,嚴格相等算法是相等算法的子集因為 11.9.6.2–7 對應 11.9.3.1.a–f。

了解相等比較的模型EDIT

在 ES2015 以前,你可能會說雙等和三等是“擴充”的關系。比如有人會說雙等是三等的擴充版,因為他處理三等所做的,還做了類型轉換。例如 6 == "6" 。反之另一些人可能會說三等是雙等的擴充,因為他還要求兩個參數的類型相同,是以增加了更多的限制。怎樣了解取決于你怎樣看待這個問題。

但是這種比較的方式沒辦法把 ES2015 的 

Object.is

 排列到其中。因為 

Object.is

 并不比雙等更寬松,也并不比三等更嚴格,當然也不是在他們中間。從下表中可以看出,這是由于 

Object.is

 處理 

NaN

 的不同。注意假如 

Object.is(NaN, NaN)

 被計算成 

false

 ,我們就可以說他比三等更為嚴格,因為他可以區分 

-0

 和 

+0

 。但是對 

NaN

 的處理表明,這是不對的。 

Object.is

 應該被認為是有其特殊的用途,而不應說他和其他的相等更寬松或嚴格。

判等

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

true

true

true

+0

-0

true

true

false

false

true

false

false

""

false

true

false

false

""

true

false

false

"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

null

false

false

false

NaN

false

false

false

"foo"

NaN

false

false

false

NaN

NaN

false

false

true

什麼時候使用 

Object.is

 或是三等EDIT

總的來說,除了對待

NaN

的方式,

Object.is

唯一讓人感興趣的,是當你需要一些元程式設計方案時,它對待0的特殊方式,特别是關于屬性描述器,即你的工作需要去鏡像

Object.defineProperty

的一些特性時。如果你的工作不需要這些,那你應該避免使用

Object.is

,使用

===

來代替。即使你需要比較兩個

NaN

使其結果為

true

,總的來說編寫使用

NaN

 檢查的特例函數(用舊版本ECMAScript的

isNaN方法

)也會比想出一些計算方法讓

Object.is

不影響不同符号的0的比較更容易些。

這裡是一個會差別對待-0和+0的内置方法和操作符不完全清單:

- (一進制負)

顯而易見,對

0一進制負操作得到

-0

。但表達式的抽象化可能在你沒有意識到得情況下導緻-0延續傳播。例如當考慮下例時:
let stoppingForce = obj.mass * -obj.velocity
           
如果

obj.velocity

是  (或計算結果為 ), 

一個-0

就在上處産生并被指派為

stoppingForce的值

.

Math.atan2

Math.ceil

Math.pow

Math.round

即使傳入的參數中沒有-0,這些方法的傳回值都有可能是-0。例如當用 

Math.pow

計算

-Infinity

的任何負奇指數的幂都會得到

-0

。詳情請參見這些方法各自的文檔。

Math.floor

Math.max

Math.min

Math.sin

Math.sqrt

Math.tan

當傳入參數中有-0時,這些方法也可能傳回-0。例如, 

Math.min(-0, +0)

 得出 

-0

。詳情請參見這些方法各自的文檔。

~

<<

>>

這些操作符内部都使用了ToInt32算法。因為内部32位整數類型隻有一個0(沒有符号差別),-0的符号在反操作後并不會保留下來。例如

Object.is(~~(-0), -0)

Object.is(-0 << 2 >> 2, -0)

都會得到false

.

在未考慮0的符号的情況下依賴于

Object.is

是危險的。當然,如果本意就是區分-0和+0的話,

Object.is

能按照期望完成工作。

繼續閱讀