JavaScript 裡的各種計較

這篇原本只想記下 NaN±0Object.is(), 但 MDN 上已經有一篇相等比較的文章了,我就來整理一下 ES2015 官方文件裡面那些抽象的操作。內容基本上都在文件裡面的 7.2 這個章節中所提到的 13 個模式:

我大致上把這 13 種模式區分一下:

  • 是否同類
  • 是否相等
  • 其他 此類待完善

內容的說明會依照程式的判斷順序下去講解,如下:

  1. 第一步
  2. 第二步
  3. 第三步
  4. 諸如此類……

當然內容以我根據官方規範所理解的來講,內容可能還是會有所紕漏。待以後瞭解詳細的細節會再完善,也歡迎讀者提供資訊以及文中的錯誤。

是否同類

isArray()

一般實作為 Array.isArray() 來調用。

  1. 如果型別不屬於物件型別,像是:Undefinied、Null、布林、字串、數字等基型值 (Primitive Value),包括了新的 Symbol,直接回傳 false
  2. 如果是一個陣列物件 (Array Exotic Objects),則回傳 true
    這個部分比較難懂,大致上可以理解為我們平常所使用到的陣列,但不包含類陣列 (Array-like) 的型式。
  3. 如果是一個代理物件 (Proxy Exotic Objects),則會:
    1. 檢查代理處理器 (Proxy Handler) 是否為 null,若為 null 則拋出 TypeError 錯誤。
    2. 將代理目標 (Proxy Target) 作為引數傳入 isArray() 中再進行判斷。
  4. 其他則回傳 false

MDN 上的實作參考如下:

if (!Array.isArray) {
  Array.isArray = function(arg) {
      return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

isInteger()

一般實作為 Number.isInteger() 來調用。

  1. 如果型別不屬於數字型別,直接回傳 false
  2. 如果是 NaNInfinity-Infinity,則回傳 false
  3. 如果傳入的引數 (argument) 的絕對值不等於本身絕對值下取整後的結果,則回傳 false
    下取整意為尋找一個不超過其本身的整數中裡面最大的那一個整數,簡單的例子:傳入 5.5 後,下取整就是找比 5.5 還要小的整數們,像是:0、1、2、3、4、5 這些,接著找這些整數中最大的那一個!也就是 5,這也就是說「5 就是 5.5 的下取整結果」。
    要特別注意浮點數中的精度,像是 1.0000000000000001 這樣的例子。
  4. 其他則回傳 true

MDN 上的實作參考如下:

Number.isInteger = Number.isInteger || function(value) {
  return typeof value === 'number' &&
    isFinite(value) &&
    Math.floor(value) === value;
};

IsRegExp()

這個方法沒看到瀏覽器實作,但 underscore.js 與 lodash 中有相關的函式可以使用。但好像沒有依照 ECMA 的標準下去執行,另外瀏覽器對於 RegExp 的支援度好像也各有不同。

  1. 如果型別不屬於物件型別,直接回傳 false
  2. Let isRegExp be Get(argument, @@match). 這個不太會解釋
  3. 非衝突則繼續,否則 return。
  4. 如果 IsRegExp() 不是 undefinied,則回傳 IsRegExp() 的布林化結果。
  5. 如果有 RegExpMatcher 的物件內部 slot,則回傳 true
  6. 其他則回傳 false

是否相等*

SameValue()

同值相等,一般實作在 Object.is() 的比較方法上。

  1. 檢查傳入引數,兩者皆非衝突則繼續,否則 return。
  2. 如果左邊型別與右邊型別不同,則回傳 false
  3. 如果左邊是 undefinied,則回傳 true
  4. 如果左邊是 null,則回傳 true
  5. 如果左邊是數字型別:
    1. 若兩者皆為 NaN,則回傳 true
    2. 若左邊是 +0 而右邊是 -0,則回傳 false
    3. 若左邊是 -0 而右邊是 +0,則回傳 false
    4. 若左邊的數值與右邊的數值相等,則回傳 true
    5. 其他則回傳 false
  6. 如果左邊是字串型別:
    1. 兩者具有相同的字串長度與相對應的程式碼序列時回傳 true
    2. 其他則回傳 false
  7. 如果左邊是布林型別:
    1. 兩者皆為 true 或皆為 false,則回傳 true
    2. 其他則回傳 false
  8. 如果左邊是 Symbol 型別:
    1. 兩者具有相同的 Symbol 值時回傳 true
    2. 其他則回傳 false
  9. 如果兩者為相同的物件時回傳 true,其他則回傳 false
    相同意指在兩者皆指向 (refer to) 同一個底層物件,當作是同樣的物件參照 (by same reference) 應該比較好理解。

SameValueZero()

零值相等,一般實作在 Map 物件中判斷「鍵的相等性」。

  1. 檢查傳入引數,兩者皆非衝突則繼續,否則 return。
  2. 如果左邊型別與右邊型別不同,則回傳 false
  3. 如果左邊是 undefinied,則回傳 true
  4. 如果左邊是 null,則回傳 true
  5. 如果左邊是數字型別:
    1. 若兩者皆為 NaN,則回傳 true
    2. 若左邊是 +0 而右邊是 -0,則回傳 true
    3. 若左邊是 -0 而右邊是 +0,則回傳 true
    4. 若左邊的數值與右邊的數值相等,則回傳 true
    5. 其他則回傳 false
  6. 如果左邊是字串型別:
    1. 兩者具有相同的字串長度與相對應的程式碼序列時回傳 true
    2. 其他則回傳 false
  7. 如果左邊是布林型別:
    1. 兩者皆為 true 或皆為 false,則回傳 true
    2. 其他則回傳 false
  8. 如果左邊是 Symbol 型別:
    1. 兩者具有相同的 Symbol 值時回傳 true
    2. 其他則回傳 false
  9. 如果兩者為相同的物件時回傳 true,其他則回傳 false
    相同意指在兩者皆指向 (refer to) 同一個底層物件,當作是同樣的物件參照 (by same reference) 應該比較好理解。
    注:與同值相等差別在 +0 與 −0 的判斷。

===

嚴格相等,就是在 JavaScript 中常使用的三等號。

  1. 檢查傳入引數,兩者皆非衝突則繼續,否則 return。
  2. 如果左邊型別與右邊型別不同,則回傳 false
  3. 如果左邊是 undefinied,則回傳 true
  4. 如果左邊是 null,則回傳 true
  5. 如果左邊是數字型別:
    1. 若兩者皆為 NaN,則回傳 false
    2. 若左邊是 +0 而右邊是 -0,則回傳 true
    3. 若左邊是 -0 而右邊是 +0,則回傳 true
    4. 若左邊的數值與右邊的數值相等,則回傳 true
    5. 其他則回傳 false
  6. 如果左邊是字串型別:
    1. 兩者具有相同的字串長度與相對應的程式碼序列時回傳 true
    2. 其他則回傳 false
  7. 如果左邊是布林型別:
    1. 兩者皆為 true 或皆為 false,則回傳 true
    2. 其他則回傳 false
  8. 如果左邊是 Symbol 型別:
    1. 兩者具有相同的 Symbol 值時回傳 true
    2. 其他則回傳 false
  9. 如果兩者為相同的物件時回傳 true,其他則回傳 false
    相同意指在兩者皆指向 (refer to) 同一個底層物件,當作是同樣的物件參照 (by same reference) 應該比較好理解。
    注:與同值相等差別在 ±0 與 NaN 的判斷。

==

一般相等,或是文件上所說的抽象相等,也是我們在 JavaScript 中常使用的雙等號。

  1. 檢查傳入引數,兩者皆非衝突則繼續,否則 return。
  2. 若兩者皆是同樣型別,則調用 === 嚴格相等比較。
  3. 如果左邊是 null 而右邊是 undefinied,則回傳 true
  4. 如果左邊是 undefinied 而右邊是 null,則回傳 true
  5. 如果左邊是數字型別而右邊是字串型別,將右邊轉為數字型別再進行 == 一般相等比較。
  6. 如果左邊是字串型別而右邊是數字型別,將右邊轉為字串型別再進行 == 一般相等比較。
  7. 如果左邊是布林型別,將左邊轉為數字型別再進行 == 一般相等比較。
  8. 如果右邊是布林型別,將右邊轉為數字型別再進行 == 一般相等比較。
  9. 如果左邊是字串數字Symbol 且右邊是物件,將右邊轉為基型值後再進行 == 一般相等比較。
  10. 如果左邊是物件且右邊是字串數字Symbol,將左邊轉為基型值後再進行 == 一般相等比較。
  11. 其他則回傳 false
    物件轉型可參考 JavaScript 型別轉換的那些事

對照

同值相等、零值相等和嚴格相等的差異對照

SameValue(x,y) SameValueZero(x,y) x===y
NaN true true true
±0 false true false

四種相等的比較

參考 MDN 範例

其他

關係比較 x < y

雖然比較運算對各種型別的運算元來說都可以進行估算,但關係比較實際上只會對數字字串這兩種基型值進行估算。估算時還會因為 JavaScript 中所指定的自左至右的表達式估算順序以及 ECMAScript 規範中所提到的 LeftFirst 影響,但這個部分我看了也不是很懂就先參考 JavaScript 大全 /6e 中的結合 ECMA-262 規範整理規則。

  1. 檢查傳入引數,兩者皆非衝突則繼續,否則 return。
  2. 如果有任一引數為物件型別,則將其轉為基型值再進行比較。
  3. 會先將轉換後的值作為 px 以及 py 來繼續執行估算。
  4. 如果 px 以及 py 兩者轉換後皆為字串型別,則會依照字母順序的比較:
    1. py 這個字串是 px 的前綴 (即使 字串 pxpy 加上一個空字串) 則回傳 false
    2. px 這個字串是 py 的前綴則回傳 true
    3. 找出兩者中最先出現不同的字元程式碼 (一般為 16 位元的 Unicode,UTF-16) 序列點的值作為 mn,若 m<n 則回傳 true,否則回傳 false
  5. 如果 px 以及 py 兩者轉換後有任一個不是字串型別,則將 px 以及 py 兩者轉換為數字型別的 nx 以及 ny 來繼續執行估算,兩者皆非衝突則繼續,否則 return。
  6. 如果 nxNaN,則回傳 undefined
  7. 如果 nyNaN,則回傳 undefined
  8. 如果兩者是相同的數值則回傳 false
  9. 如果 nx+0ny-0,則回傳 false
  10. 如果 nx-0ny+0,則回傳 false
  11. 如果 nxInfinity,則回傳 false
  12. 如果 nyInfinity,則回傳 true
  13. 如果 ny-Infinity,則回傳 false
  14. 如果 nx-Infinity,則回傳 false
  15. 如果 nx 在數學上的數值比 ny 在數學上的數值來的小,則回傳 true,否則回傳 false
    物件轉型可參考 JavaScript 型別轉換的那些事
    數學上的數值已經限定在兩者皆有限以及兩者皆非零的集合
    在小於等於 <= 的關係判斷中所代表的是不大於的判斷,也就是回傳 !(x > y),反之亦然

以下待補


物件強制轉型 RequireObjectCoercible()

是否能被呼叫 IsCallable()

判斷該引數是否為一個具備外部間接調用的方法的函式

是否為建構式 IsConstructor()

判斷該引數是否為一個具備建構函式的函式物件 (function object)。

是否能夠擴充 IsExtensible()

判斷該引數是否能在該物件上加入新的特性

是否能作為特性 IsPropertyKey()

判斷該引數是否能在物件中作為一個特性