[25] 強制轉型 - 隱含的強制轉型、Addition Operator、Strings <> Numbers


keywords:implicit coercion,Addition Operator,Strings <> Numbers

隱含的強制轉型 ( implicit coercion )

  • 隱含的強制轉型就是 ( 對你來說 ) 不明顯且有副作用的任何型別轉換動作
  • 明確的強制轉型就是讓程式碼更清晰明白,更容易理解;隱含的強制轉型就是完全相反的東西,讓程式碼更難以理解。
  • JavaScript 隱含的強制轉型是抱怨強制轉型的主要來源,但隱含的強制轉型是邪惡的嗎? 作者認為也許可以從不同的角度來看看隱含的強制轉型是什麼,可以怎麼用,而非只是說『 它是好的明確強制轉型的反面 』
  • 自己認為如果一個程式語言能夠為大家所用,那一定有其設計語言的便利性及原因,不然請非本科系的學 Java 或 C# 等物件導向的語言,我想應該沒幾個有興趣吧
  • 隱含的強制轉型目標是為了減少冗長、反覆套用,以及非必要的實作細節,降低程式碼雜亂,以專注於更重要的目的

簡化隱含

先來看一個例子:
SomeType a = SomeType(AnotherType(b));
在這個例子中想要把擁有其他型別 b 轉為 SomeType,但這個語言無法直接從 b 目前的型別轉為 SomeType,它需要先轉為 AnotherType 再從 AnotherType 轉為 SomeType

現在有一種語言能夠這樣做:
SomeType a = SomeType(b);
大家不會同意在此簡化了型別轉換動作,減少了非必要的中間轉換型別的步驟嗎? 意思是看到並處理『 b 會先轉為 AnotherType,然後再變成 SomeType 』這個事實,真的有這麼重要嗎? 🤔

簡化的動作實際上增進了程式碼的可讀性

作者給大家的鼓勵是:不要滿足於此。別在到洗澡水時,連小嬰兒都一起倒掉了,別假設隱含的強制轉型全都是壞的

自己的想法是後端型別的轉換明確是很重要的,因為後端需要處理大量資料邏輯,且與資料庫有熱絡的溝通,如果型別不精確,會導致資料不精確造成公司損失,例如:原本需要精度高的型別 float 但卻用了 integer 尤其在銀行或是科學界,小數位數可能就會造成外幣兌換數目落差及火箭爆炸等災難;相對於前端 JavaScript 精確的型別並不是這麼重要,前端是注重使用者體驗的介面,使用者根本不會在意看到的數字是文字型別或數字型別

隱含地:Strings <> Numbers

[23] 強制轉型 - 明確的強制轉型、Strings <> Numbers、日期轉 Number、位元運算子有講到明確地 Strings <> Numbers 轉型,在開始講隱含地轉型前,先檢視一下隱含地強制進行型別轉換作業的一些細微之處

  • +運算子被重載了 ( overloaded ),用於 number 的相加以及 string 的串接這兩項工作,但 JavaScript 如何得知要哪種作業呢?

      ex1:
    
      let a = '98';
    
      let b = '7';
    
      let c = 98;
    
      let d = 7;
    
      a + b; // '987'
    
      c + d; // 105
    
      ---------------------------------------
    
      ex2:
    
      let x = [1,2];
    
      let y = [3,4];
    
      x + y; // '1,23,4'
    

    常見的誤解是在於運算元是否有一個或兩個是 string,表示 + 會假設要進行的是 string 的串接,但在 ex2 又好像並非如此, 🤔 x y 皆非 string 但顯然它們都被強制轉型成了 string ,但為何不是強制轉型成 number?

  • 根據 2021 語言規格的 12.8.3 The Addition Operator ( + ) 參考到的 12.15.5 Runtime Semantics: ApplyStringOrNumericBinaryOperator
    addition operator
    步驟如下:
    1. 運算子為 +
      1-1. 將左右參數使用 ToPrimitive 抽象運算取得原生型別值
      1-2. 如果左右參數其中一個型別為 string ,就將雙方都用 ToString 轉成 string 並做字串的連結並回傳
    2. 非 + 運算子或如果左右參數在使用 ToPrimitive 後皆非 string 型別,那一定是數字的運算
      2-1. 原生型別值做 ToNumber,如果轉型後的左右參數型別不相同,擲出一個 TypeError
      2-2. 如果 ToNumber 後左右參數相同就做數字的運算並回傳

Note:所有的標準物件 ToPrimitive 的 hint 都是 Number 除了 Date 物件,換句話說

  • 除了 Date 物件以外的物件 ToPrimitive 的順序:valueOf() 優先如果回傳非基本型別值再進行 toString()
  • Date 物件的 ToPrimitive 的順序:toString() 優先如果回傳非基本型別值再進行 valueOf()

      let a = new Date();
    
      a.valueOf(); // 1590822045060
    
      a.toString(); // "Sat May 30 2020 15:00:45 GMT+0800 (台北標準時間)"
    
      a + 9; // "Sat May 30 2020 15:00:45 GMT+0800 (台北標準時間)9"
    
      a.toString = null; // 將 toString 方法設置為 null 😏
    
      a + 9; // 1590822045069  ( 1590822045060 + 9 )
    

    蠻有趣的啊 XD

所以講解上面 x y 的例子:
1.上面兩個 x y [] 串接的步驟會先走到 1-1.將左右參數使用 ToPrimitive,而 Array 非 Date 物件所以 hint 為 Number 也就是會先執行 valueOf() ,但沒辦法產生一個簡單的基型值而失敗
2.所以它改用 toString() 表示法
3.根據步驟 1-2 雙方其中一個型別為 string 故進行 ToString 進行字串的連結

    step 1:

    x.valueOf(); // [1,2]

    y.valueOf(); // [3,4]

    ---------------------------------------

    step 2:

    x.toString(); // '1,2'

    y.toString(); // '3,4'

    ---------------------------------------

    step 3:

    '1,2' + '3,4'; // '1,23,4'

Tips:如果 + 的任一邊運算元是 string ( 或是透過上面的步驟 1-1 能變成一個字串 ),那麼進行的動作就會是 string 的串接,否則的話一定會是數值的加法運算

程式裡我們經常使用這個隱含的強制轉型:

    let a = 98;

    let b = a + '';

    b; // '98'

a + '' a 會先使用 ToPrimitive 調用 valueOf() 後得到 number 基本型別值,便會停止不再調用 toString() ,但因 '' 為一個 string 型別值,所以雙方都會使用 ToString 被轉為一個 string。

a + ''String(a) 都會產生一個 string ,但如果使用的是 object 而非正規的 number 基本型別值,不一定會得到相同的 string 值,因為 String(a) 使會直接調用 toString() 方法的

    let a = {

      valueOf: function(){ return 98 },

      toString: function(){ return 7 }

    };

    a + ''; // '98'

    String(a); // '7'

如果今天要把 string 隱含地強制轉型為 number 呢?

    let a = '98.87';

    let b = a - 0;

    b; // 98.87

( - ) 運算子被定義用於數值減法運算,所以 a - 0 會使 a 的值被強制轉型為一個 number , a * 1 或 a / 1 也能達成相同的效果,因為這些運算子同樣也只定義給數值運算使用。

    let x = [9];

    let y = [7];

    x - y; // 2

這兩個 array 值都必須變成 number,先進行 valueOf() 回傳還是陣列,故再經由 toString() 轉為基本型別值 string,然後再強制轉型為 ToNumber,以便進行 - 的減法運算

最後比較 明確的 b = String(a) vs. 隱含的 b = a + '' 這兩種做法都能說是有用的,但 b = a +'' 在 JS 程式中要常見得多,這證明了它的實用性。

#implicit coercion #Addition Operator #Strings <> Numbers







你可能感興趣的文章

打包程式碼必備 bundler : Webpack

打包程式碼必備 bundler : Webpack

[筆記] React 隨手記 (React 抽獎機率設定)

[筆記] React 隨手記 (React 抽獎機率設定)

從 V8 bytecode 探討 let 與 var 的效能問題

從 V8 bytecode 探討 let 與 var 的效能問題






留言討論