[23] 強制轉型 - 明確的強制轉型、Strings <> Numbers、日期轉 Number、位元運算子


keywords:explicit coercion,Strings <> Numbers,Date <> Numbers,bitwise operators

明確的強制轉型 ( explicit coercion )

目標是辨識出程式碼中的某些模式,更清楚地表示要將某個值從一個型別轉為另一個,留下讓未來開發人員一看就理解的程式碼

明確地:Strings <> Numbers

要在 string 與 number 之間進行強制轉型,我們會使用內建的 String(..) 、 Number(..),要注意的是我們不在前面加 new 因為使用 new 關鍵字等於是在建立物件包裹器 ( object wrappers ),a 會是一個物件型別

    let a = new String('123');

    let b = String('123');

    let c = '123';

    typeof a; // object

    🚩 b === c; // true 這兩種寫法是完全相等的

    typeof c; // string

String(..) 及 Number(..) 會把任何的值強制轉型為 string / number 基本型別值 ( primitive value )
使用的運算規則就如前兩章提到的方法:

除了 String() 與 Number() ,還有可以在 string 與 number 兩者之間明確地進行轉換

    let a = 99;

    a.toString(); // '99'

    let b = '1.5';

    +b; // 1.5
  • 這裡的 a 看起來是明確的轉換,但實際上仍然有隱含性,toString() 不能在像 99 這樣的基本型別值上被呼叫,我們在前面 [17] 原生功能 Natives - 內部特性、封裝、解封裝 有做過筆記:

    基本型別值沒有特性 ( properties ) 或方法 ( methods ),所以要存取 .length 或 .toString() 時會需要包裹基型值的物件包裹器,幸好 JavaScript 會自動封裝 ( box 也就是 包裹 wrap )

    所以其實 JavaScript 有自動把 a 封裝在一個物件包裹器,如此 toString() 能夠在該物件上被呼叫,可以稱為『 明確地隱含 ( explicitly implicit ) 』🤔

  • +b 中 + 運算子的單運算元 ( unary operator ) 形式 ( 即只有一個運算元的運算子,簡單說就是只有一個要計算的變數碰上的 + ),不會進行數學的加法運算或字串串接動作,單元的 + 會明確地把運算元 b 強制轉型為一個 number 值
  • 普遍的觀點是 + 可被接受為明確強制轉型的一種形式,不過要取決於我們的經驗與觀點,如果我們知道單運算元 + 是明確的進行 number 的強制轉型,就是明確的,如果我們從未看過它.對我們來說就是隱含的、令人困惑的、有隱藏副作用的
  • 罷特! 不能把兩個加號相連放在隔壁 ( ++ ),這樣會被解讀為遞增運算子,所以應該要在中間放一個空格 3+ +b

      let b = '1.5';
    
      3+ +b; // 4.5
    
      3++b; // SyntaxError: Invalid left-hand side expression in postfix operation
    
  • 也不能將兩個減相連放在隔壁 ( -- ) 試圖要做一個負負得正的數學運算,因為這樣會被解讀為遞減運算子,如果想要負負得正一樣中間要放一個空白 - -b

      - -b; // 1.5
    
      --b; // 0.5
    
  • 如果有與其他運算子相鄰的運算,建議避免使用單運算元 + / - ,因為會使人混淆,降低可閱讀性

日期轉為數字

  • 單元 + 運算子除了用在 string 強制轉型為 number 之外,常見的另外一個用途是將 Date 物件強制轉型為一個 number,因為會是日期時間值的 Unix ( Unix timestamp 從 1970 年 1 月 1 日 00:00:00 UTC 開始計算的毫秒數) 時戳

      let a = new Date('Sun,24 May 2020 10:40:43 GMT+8'); // Sun May 24 2020 10:40:43 GMT+0800 (台北標準時間)
    
      +a; // 1590288043000  這個時間點的時間戳
    
      typeof a; // object
    
      typeof +a; // number
    
  • 日期物件最常的使用情境是取得當下的時間戳

      +new Date(); // 1590288043000
    
  • JavaScript 中一個奇特的語法,只有在沒有引數要傳入時,建構器呼叫 ( 帶有 new 函式呼叫 ) 的 () 會是選擇性的,+new Date ,但那個 () 會提升易讀性,並非所有開發人員都同意省略

      +new Date; // 1590288043000
    
      +new Array; // 0
    
      +new String; // 0
    
  • 不過作者建議不要使用與日期有關的強制轉型形式,要產生現在的時戳夠好的做法就用 ES5 新增的 Date.now() 靜態函式
  • 如果要取得所指定的非現在日期時間之時戳,就使用 new Date().getTime();

      Date.now(); // 1590288043000
    
      new Date('Sun,24 May 2020 10:40:43 GMT+8').getTime(); // 1590288043000
    
      這邊要注意不能只寫 GMT 因為那會是格林時間 +0 台灣是 +8 所以要記得寫 GMT+8
    
  • 如果想要把時間戳轉成日期時間,可以使用 new Date(時間戳)
      new Date(1590288043000); // Sun May 24 2020 10:40:43 GMT+0800 (台北標準時間)
    

~位元運算子

  • 位元運算子將運算元視為一段帶號的 32 位元整數的 0 和 1 序列,而不是十進位、十六進位或八進位的 Number
    看一下 規格書 2021 7.1.5 ToInt32:
    ToInt32

接著看一下 MDN 文檔的位元運算子描述:
bitwise operators

  • 反轉的意思就是把二進制的 1 轉成 0 0 轉成 1

      3 = 00 00000 00000 00000 00000 00000 00011
    
     ~3 = 11 11111 11111 11111 11111 11111 11100
    
     二補數系統確保了正值時最左邊的位元為 0,反之則為 1。因此,最左邊的位元被稱作符號位。
    
     如果要把一個負的二進制轉成正數,就要把 1 轉成 0  0 轉成 1 然後再 +1 但前面要加一個負號
    
     ~3 = 11 11111 11111 11111 11111 11111 11100
    
        = 00 00000 00000 00000 00000 00000 00011   +1
    
        = 00 00000 00000 00000 00000 00000 00100
    
        = 100  前面 0 省略
    
        = (1*2^2) + (0*2^1) + (0*2^0)  ^ 符號這邊的意思是用數學次方的意思
    
        = 4 + 0 + 0 不要忘記負號
    
        = -4
    
    ∴ ~3 等於 -4
    
  • 簡單說 ~ 會先強制轉型為一個 32 位元的 number 值,然後進行反轉, ~x 大略等於 -(x+1)

      ~3; // -(3+1) equal  -4
    
  • 數字中唯一會使 Boolean ( x ) 為 false 的數字是 0 / -0,所以 ~-1 就會產生 0 ,-1 常被稱為哨符值 ( sentinel value ) 被賦予了一個特殊意義

  • C 語言中有許多函式都使用 -1 作為哨符值, -1 代表失敗
    JavaScript 中 indexOf(..) 就採用了此先例,通常 indexOf() 都還會被檢查是否為 boolean ,判斷某個子字串是否出現在另外一個 string 中, 例如:

      let a = 'You dont know JS';
    
      a.indexOf('ow') >= 0; // true 有找到
    
      a.indexOf('wo') >= 0; // false 沒找到
    
  • 這是一種容易洩漏資訊的抽象層,它把底層的實作行為,-1 表示失敗洩漏到程式碼中,這時候 ~ 就派上用場了

      ~a.indexOf('ow'); // -12 有找到
    
      ~a.indexOf('wo'); // 0 沒找到
    

    嚴格說 if(~a.indexOf(..)) 仍然仰賴隱含的強制轉型,它將結果 0 轉為false

#explicit coercion #String<>Numbers #Date<>Numbers #bitwise operators







你可能感興趣的文章

金魚系列、稀飯版

金魚系列、稀飯版

Vim外掛配置與vimrc

Vim外掛配置與vimrc

Day06 : Component 的三種長相

Day06 : Component 的三種長相






留言討論