神奇的JavaScript弱等價型別轉換

CamWang發表於2024-06-11

JavaScript語言特性 - 型別轉換

JavaScript這門語言的型別系統從來沒有它表面看起來的那樣和善,雖然比起Java、C#等一眾強型別語言,它的弱型別使用起來似乎是如此便利,但正因為它極高的自由度,所以才會衍生出令人摸不著頭腦的荒誕行為。
舉個例子,雖然我們都知道一個包含內容的字串會被認為是“真值 Truthy”(因為除了空字串之外任何字串在JS裡都被認為真值),但當你做如下比較的時候,你會得到一個驚掉下巴的結果

const a = "18";
const b = true;

a == b  // false

什麼鬼,一個被通常理解成真值的值,竟然無法與布林真值鬆散相等?
為了能撥開JavaScript型別的迷霧,讓頭鐵的我們一點一點理順JavaScript整個型別系統的工作邏輯。

讀者可以根據自己對JS型別系統的掌握程度,選擇性的閱讀這篇部落格

型別基礎

JavaScript有以下八大型別,除了object型別,其他都為基本型別

  • number
  • string
  • boolean
  • null
  • undefined
  • object
  • symbol
  • bigint

他們的型別都可以直接被typeof識別,特例是

  • typeof null"object"雖然它是null型別的值
  • typeof function(){}"function"雖然它理論上是object型別的

雖然你可能已經為這種特例所不解,但其實這才剛開始,大的還在後面

型別轉換

轉換為數字

JavaScript內部有一套抽象的數字轉換機制叫ToNumber,這套機制在隱式轉換或者部分顯式轉換其他型別值到數字時會被呼叫。雖然你可能會被噁心到,但我還是要向你介紹這套機制的規則為

  • string若為數字表示式則轉換為其對應數字,否則返回NaN
  • undefined轉換為NaN
  • null轉換為0
  • true轉換為數字1
  • false轉換為數字0
  • object型別會依次呼叫toPrimitive()valueOf()toString()來獲取值,並利用上面的規則獲取其數字值

ToNumber的轉換規則會在以下情況下使用,當然這些情況也可以稱作轉換數字的“技巧”,看你怎麼理解它了:

  • Number(value)
  • + value
  • Math.floor(value)
  • value * x,將一個值做乘法運算

沒錯,Math.floor(true)+ truetrue * 1都等於1,是不是覺得很荒誕?

'5' + 3或者5 + '3'結果都是字串'53',因為+在有兩個操作值,且其中一個為字串時,會直接做字串拼接。所以雖然+ '3'結果為數字3,但5 + '3'的結果不是8

parseInt()parseFloat()只接受string型別,所以轉換規則與ToNumber轉換機制下的string型別情況相似,但是在處理字元時採取從左到右的掃描直到失敗為止的方法,所以parseInt("123hello")結果為123

轉換為布林

JavaScript還有一套針對布林型別的抽象轉換機制叫ToBoolean。因為對於前端邏輯編寫來講,判斷一個值是否為真實在太重要了,JavaScript裡變數像薛定諤的貓一樣,處於存在與不存在、真和假的中間態,所以我們JS開發者都有一個奇怪的腦回路,當看到一個字面量值的時候就開始評估它是“真值”(Trusy Value)還是“假值”(Falsy Value)。
可是,與物理學裡薛定諤的貓現象相反,JavaScript裡對真假值的定義其實很簡單,以下的值均為假值

  • undefined
  • null
  • false
  • +0-00nNaN
  • ""

其他均為真值,任何非空字串、非0數字、物件都是真值。

轉換其他型別值為布林型別的方法:

  • !!value
  • Boolean(value)
  • !,可將值轉換為布林型別,但是真假結果相反

會隱式的將其他型別值轉換為布林的情況:

  • &&
  • ||
  • if (value)
  • while (value)
  • for (...; value; ...),for迴圈的第二個測試表示式
  • ? :,三元運算子

恭喜你勇士,讀到這裡就代表馬上你就能知道為什麼"18" != true了!!!

等價性

我們都知道,JS當中有鬆散判斷的弱等價==,和嚴格判斷的強等價===兩種判斷等價方式。強等價要求兩個操作值必須為同一型別,且值本身也相等,其行為非常容易預測。弱等價在比較值是否相等前會嘗試做一些型別轉換,儘可能的讓可能為不同型別的兩個值變得可以判斷。
造成文章開頭神奇判斷結果的原因就在弱等價==時的型別轉換策略上,聽我將弱等價的轉換規則為你娓娓道來

  • 數字與字串比較,則將字串轉換為數字
  • 布林值與任何其他值作比較,都先將布林值轉換為數字
  • nullundefined鬆散比較結果相等
  • 物件與數字或字串比較,先呼叫物件的toPrimitive()獲取原始型別值後進行比較

所以,"18" == true進行比較時

  • 由於布林值的比較規則為將布林值轉換為數字,ToNumber(true)結果為數字1,表示式變為"18" == 1
  • 又因為字串與數字比較時字串應轉換為數字,則ToNumber("18")結果為數字18
  • 最後表示式實際比較18 == 1,結果為假false

所以沒錯,以下表示式均為真:"1" == true"0" == falsefalse == ""

結語

瞭解這個例子後你可能會對JavaScript語言行為設計混亂表達不滿,但是上述邊界條件可以透過引入TypeScript,避免弱等價判斷轉而使用嚴格等價,在隱式型別轉換之前使用可預測行為的轉換方法對值提前進行處理。
如果你無法採用上面列舉的解決辦法,那我的建議就是多多關注我的部落格,如果覺得不錯可以推薦給你的朋友或同事,說不定就能在身邊慢慢帶動大家採用更令人舒心的寫法嘞。

ps. 辛苦讀者了,為大家的好奇心與耐心致敬。

相關文章