iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
1
Modern Web

跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset系列 第 12

[day11] YDKJS (Equality) : 一般相等 == 比較「值」 , 嚴格相等 === 比較 「型別和值」是錯的?

  • 分享至 

  • xImage
  •  

Coercion (型別轉換)其實最令人煩惱的不是 一元運算子的 + 或是 加法運算子的 多載問題。

其實最多人一開始採的坑是 Equality (相等) 問題。

以下是最常見的描述:

  1. 兩個 == 一般相等 比較
  2. 三個 === 是嚴格相等,比較 型別

但很抱歉,這樣寫都是錯的。

一般相等 Spec. Abstract Equality Comparison 7.2.14

這邊是 Spec.

我們可以把目光放在 第一行

  1. 如果型別一樣,其實== 一般相等 等效 === 嚴格相等

也就是說,如果你了解 code 裡面寫的型別都一樣,可以放心用 == 一般相等

嚴格相等 Spec. 7.2.15 Strict Equality Comparison

  1. 如果型別不同,直接回傳 False
    (也就是連不會比較)

  2. 先比較數字型別的狀況
    (a.) (b.) 實作 IEEE 754 定義 (NaN ≠ x) = false.

之前有寫過 NaN,參考
[day06] YDKJS (Type) : 特殊值:undefined / undeclared / TDZ ? , NaN , 負數 0

  1. (c.) 數字型別的條件下,如果 兩邊 相等就回傳 true
  2. (d.) (e.) 這邊是一個 嚴格相等壞掉的地方,正零負零不會比較,都回傳 true

之前有寫過 負數 0,參考
[[day06] YDKJS (Type) : 特殊值:undefined / undeclared / TDZ ? , NaN , 負數 0]

嚴格相等( === )大家從小就會,補充閱讀 Spec.

Null , undefined 可以繼續比較一定同型別,且該型別只有一個值,當然會相等
string相同的長度相同的字元對應相同的位置 才會回傳 true
Boolean 只有兩個值,比對是否相等很容易
Symbol Symbol value 一樣才會回傳 true
Object 太多問題,不建議做比對。(自己挖掘 Spec. 這邊不深入)

正確的描述是: == 允許在等價性比較中進行強制轉換,而 === 不允許強制轉換


有人說,不要使用 一般相等(==) ,因為它不可預測 ?

一個簡單的問題,如果不能預測,那 JavaScript 語言要怎麼實作?
量子 JavaScript ? 沒觀察就處於疊加態 ? ( 老高看太多 XDDD )

不如這樣說,

你要先學會,再來考慮要不要使用。

這一直是這個系列主題的宗旨:打造全新 JavaScript Mindset ,而不是一律使用 嚴格相等(===) 就不用學。

順帶一提,
如果你只使用 嚴格相等(===) ,沒認真思考過你寫的 code 其值與型別,
那麼得到 false,你會不知道其實這邊得到的 false 有兩個意涵:

  1. 如果結果 false ,是不是因為型別不一樣
  2. 如果結果 false ,是不是因為型別一樣,但其Equality comparison 失敗

所以,還是認真學習 一般相等(==) a.k.a. 抽象等價性 (之前說過型別轉換可以視為隱藏細節的抽象),把目光放在 「抽象等價性」的 Equality comparison。

「之前」參考以下文章
[day10] YDKJS (Coercion) : 如何決定何時使用「顯性」或是「隱性」的型別轉換?

一般相等 Spec. Abstract Equality Comparison 7.2.14

Coercive Equality : null == undefined

line 2, 3

JavaScript 在 抽象等價性 的比對上, null , undefined 把兩者視為相等。
有時候可能你會用 null , 有些人可能用 undefined 。
或是你只是單純想知道,是不是設定清除賦值的 undefined, null 狀態。

在某些時候,這兩個值他們會不一樣,但至少做 比對(==) 時視為相等,不用去思考那個很複雜的 ToPrimitive()

參考以下例子

你可以用抽象等價性 來查看一個屬性的「值」是不是 設定為空或是沒有初始值,

  • 如果你用 嚴格相等 ,你要把所有的狀況都寫上去,因為 null 和 undefined 型別不同

  • 如果你用 抽象等價性 ,可以把你的 code 寫比較簡潔。
    (用 null 而不使用 undefined 只是純粹因為字可以打比較少 XDDDD )

ESLine 好像會提示,Kyle Simpson 有調整這一個 rule,
best tools are the ones that are most configurable

之前有寫過
undefined 典型用法是:
1 .變數被宣告了,但沒有賦值時,就等於 undefined。
2. 呼叫函式時,應該提供的引數沒有提供,該引數等於 undefined。
3. 物件沒有沒有賦值的屬性,該屬性的值為 undefined。
4. 函式沒有回傳值時,預設回傳 undefined。
[day05] YDKJS (Type) : 初學者第一坑 - typeof 運算子, 詳解 undefined

Coercive Equality : string, number, boolean

line 4 ~ 7

抽象等價性 偏好 ToNumber()

  • (line 4, 5) number 和 String 比對 , string => ToNumber() 之後做數字相等運算 (numeric comparison)
  • (line 6, 7) 左邊或右邊是 Boolean 會轉 number, boolean => ToNumber() 之後做比對 ! ToNumber(x) == y. 或是 x == ! ToNumber(y).

再複習一下 ToNumber()

  • 空字串變成 0
  • false -> 0 , true -> 1

reference:
[day08] YDKJS (Coercion:spec) : 回來讀 spec.  ToString(), ToNumber() , ToBoolean()

參考以下例子

我自己經驗是,有時候 element.value 可能是字面值的數字(type 還是 string: "1"),
但是如果要做比較還要做型別轉換 (比如 + 一元運算子 會做 ToNumber() )。

其實在這個例子中,不用像是 line 4 做轉換,可以放心使用 抽象等價性 ( == )

Coercive Equality : 不是 Primitive value 都會做 ToPrimitive()

line 8,line 9 : top level objects type 都會做 ToPrimitive()

  1. 只要其中一邊是 top level object type,object 就會做 ToPrimitive()
  2. ToPrimitive() 之後應該會回到 Primitive (string, number, boolean) 的型別,然後抽象等價性依然做偏好數字的運算。
    (抽象演算法本身都是遞迴的)

註:不要忘記兩邊都是 object -> 型別一樣就是演算法第一行,執行 === 嚴格相等

Primitive value:

不要忘記, ToPrimitive() 是第一個講的 abstract operations
reference
[day07] YDKJS (Type/Coercion) : Type總結 和 abstract operations (Spec.)

最好的做法是,non-Primitive 就不要做 抽象等價性 ( == )

因為只會給你自己帶來麻煩。

參考
[day10] YDKJS (Coercion) : 如何決定何時使用「顯性」或是「隱性」的型別轉換?

範例如下:

拆解步驟:

line 5 : 先 ToPrimitive()
line 6 : 抽象等價性 偏好 ToNumber() => 轉成數字。

這樣其實是兩層抽象,過度抽象反而不好理解。

順帶一提,拿 array數字比較,程式可以寫得出來(因為電腦可以讀,不會報錯),
但這樣寫的背後意義不符合邏輯,沒有任何原因出現這樣的 code。
如果有,請仔細思考情境,最好適度重構。
而不是使用 嚴格相等 === 來逃避重構這些不合邏輯且沒有意義的 code。

Kyle Simpson 使用抽象等價性 ( == ) 的情境,只有 Primitive 。

從邏輯上也應該只有 Primitive 會做比對
無論任何時候,避免對 non-Primitive 做「任何比對」。

一個會有 non-Primitive 可以做 抽象等價性 ( == ) 的歷史原因,
是因為很早期習慣用 new 關鍵字產生 string , number 。

這些透過 new 產生的結果要 ToPrimitive() 才做 抽象等價性 ( == )
但是這個用法已經非常不建議使用,所以不會有這個情境了。

Summary

明天的文章會延續今天主題,以 Corner Cases 整理,或是說練習為主。


上一篇
[day10] YDKJS (Coercion) : 如何決定何時使用「顯性」或是「隱性」的型別轉換?
下一篇
[day12] YDKJS (Equality) : 不論任何 Equality,你應該「總是知道」型別。來做相等性 Cases 練習吧!
系列文
跟著 YDKJS 作者 Kyle Simpson 打造全新 JavaScript Mindset31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言