JavaScript 須要檢查變量類型嗎

19年目標:消滅英語!我新開了一個公衆號記錄一個程序員學英語的歷程javascript

有提高英語訴求的小夥伴能夠關注公衆號:csenglish 程序員學英語,天天花10分鐘交做業,跟我一塊兒學英語吧前端

javascript做爲一門動態類型語言,具備很高的動態靈活性,當定義函數時,傳入的參數能夠是任意類型。但咱們在實際編寫函數邏輯時默認是對參數有必定要求的。這也容易致使預期參數與實際參數不符的狀況,從而致使bug的出現。本文在這個層面探討javascript檢查參數的必要性。

爲何要進行類型檢查?

從兩點常見的場景來看這個問題:vue

  • 程序中指望獲得的值與實際獲得的值類型不相符,在對值進行操做的時候程序報錯,致使程序中斷。

舉個咱們最多見的調用服務端ajax請求取到返回值進行操做的例子:java

ajax('/getContent', function (json) {
    
    // json的返回數據形式
    // {data: 18}
    var strPrice = (data.data).toFixed(2);
})

若是服務端返回的數據形式以及返回的data必定是number類型,咱們這樣操做確定沒有問題。webpack

可是若是服務端返回的數據發生了變化,返回給咱們的形式變成了:程序員

{
    data: '18.00'
}

而咱們在js中並無對變量作檢測,就會致使程序報錯。es6

'18.00'.toFixed(2) // Uncaught TypeError: "18.00".toFixed is not a function
  • 跟第一點類似也是指望獲得的值與實際獲得的值類型不相符,可是對值操做不會報錯,js利用隱式類型轉換獲得了咱們不但願獲得的值,這種狀況會加大咱們對bug的追蹤難度。

舉一個也是比較常見的例子:web

/**
* input1 [number]
* input2 [number]
* return [number]
**/
function sumInput (input1, input2) {
    return input1 + input2;
}

sumInput方法的兩個入參值可能來自外界用戶輸入,咱們沒法保證這是一個正確的number類型值。ajax

sumInput(1, ''); // return '1'

sumInput方法原本指望獲得number類型的值,可是如今卻獲得了string類型的'1' 。雖然值看起來沒有變化,可是若是該值須要被其餘函數調用,就會形成未知的問題。編程

再舉一個罕見的例子:

parseInt()方法要求第一個參數是string類型,若不是,則會隱式轉換成string類型。

parseInt(0.0000008) // 8

匪夷所思吧?咱們預計這個方法的結果應該是0,但結果倒是8。在程序中咱們沒法捕獲這個錯誤,只能隱沒在流程中,最終的計算結果咱們也沒法確保正確。

緣由是parseInt(0.0000008)會變成parseInt("8e-7"),結果輸出8

類型檢查原則

因爲js語言的動態性,以及自己就沒有對類型作判斷的機制,咱們是否須要對全部變量值進行類型判斷?這樣作無疑增長了編碼的冗餘度,且無需對變量類型作檢查也正是動態語言的一個優點。

那爲了不一些由此問題帶來的bugs,咱們須要在一些關鍵點進行檢查,而關鍵點更多的是由業務決定的,並無一個統一的原則指導咱們哪裏必須進行類型判斷。

但大致趨勢上能夠參考如下我總結的幾點意見。

1、「返回值」調用外部方法獲取的值須要對類型作判斷,由於咱們對方法返回的值是有指望值類型,可是卻不能保證這個接口返回的值一直是同一個類型。

換個意思講就是咱們對咱們不能保證的,來源於外部的值都要保持一顆敬畏之心。這個值可能來自第三方工具函數的返回值,或者來自服務端接口的返回值,也多是另外一位同事寫的抽離公共方法。

2、「入參」在書寫一個函數並給外部使用的時候,須要對入參作較嚴格的類型判斷。

這裏強調的也是給外部使用的場景,咱們在函數內部會對入參作不少邏輯上的處理,若是不對入參作判斷,咱們沒法確保外部使用者傳入的究竟是什麼類型的參數。

3、「自產自銷」除了以上兩類與外部交互的場景,更多須要考慮的是咱們在編寫業務代碼時,「自產自銷」的變量該如何處理。

解釋一下「自產自銷」的意思,在編寫業務代碼時,咱們會根據業務場景定義不少函數,以及會調用函數取返回值。在這個過程當中會有入參的狀況,而這些參數徹底是本身編寫本身使用,在這種對代碼相對了解的前提下無條件的進行變量類型判斷無疑會增長編碼的複雜度。

在實際編碼中咱們更多的會使用強制類型轉換[Number String Boolean]對參數進行操做,轉換成咱們指望的類型值。具體的方式會在下一章節闡述。

如何處理和反饋變量類型與指望不符的狀況

首先談談如何判斷變量類型,咱們可使用原生js或者es6的語法對類型進行準確判斷,但更多的可使用工具庫,相似於lodash。包含了經常使用的isXXX方法。

  • isNumber
  • isNull
  • isNaN
  • ...

對變量進行類型判斷後,咱們該如何進行處理及反饋?

  • 「靜默處理」只對符合類型預期的值進行處理,不符合預期的分支不作拋錯處理。這樣作能夠防止程序報錯,不阻塞其餘與之無關的業務邏輯。
if (isNumber(arg)) {
    xxx
} else {
    console.log('xxx 步驟 獲得的參數不是number類型');
}
  • 「拋錯誤」不符合預期的分支作拋錯處理,阻止程序運行。
if (isNumber(arg)) {
    xxx
} else {
    throw new TypeError(arg + '不是number類型');
}
  • 「強制轉換」將不符合預期的值強制轉換成指望的類型。
if (isNumber(arg)) {
    (arg).toFixed(2);
} else {
    toNumber(arg).toFixed(2);
}

//可是強制轉換更多的在咱們對變量類型教有掌控力的前提下使用,因此咱們不會進行判斷,直接在邏輯中進行強制轉換。
toNumber(arg).toFixed(2);

以上三種途徑是咱們在對變量進行類型判斷後積極採起反饋的通用作法。那麼結合上一章提到的3大類型檢查原則,咱們分別是採用哪一種作法?

「返回值」調用外部函數、接口獲得的參數該如何處理反饋?

對於由外部接口獲得的值,咱們無法確保這個類型是永恆的。因此進行類型判斷頗有必要,可是到底是採用「靜默處理」、「拋錯誤中斷」仍是「強制轉換類型」呢?這裏仍是須要根據具體場景具體業務採用不一樣的方式,沒有一個恆定的解決方案。

看個例子:

// 業務代碼入口
function main () {
    
    // 監控代碼 與業務無關
    (function () {
        var shopList = getShopNameList(); // return undefined
        Countly.push(shopList.join()); // Uncaught TypeError: Cannot read property 'join' of undefined
    })()

    // 業務代碼
    todo....
}

上述例子中的咱們調用了一個外部函數getShopNameList , 在對其返回值進行操做時與主要業務邏輯無關的代碼塊出錯,會直接致使程序中斷。而對shopList進行判斷後靜默處理,也不會影響到主要業務的運行,因此這種狀況是適合「靜默處理」的。靜默處理的最大優點在於能夠防止程序報錯,可是使用的前提是這步操做不會影響其餘相關聯的業務邏輯。

若是被靜默處理的值與其餘業務邏輯還有關聯,那麼整條邏輯的最終值都會受到影響,可是咱們又靜默掉了錯誤信息,反而會增長了尋找bug的難度。

// 業務代碼入口
function main () {
    
    // 監控代碼 與業務無關
    (function () {
        var shopList = getShopNameList(); // return undefined
        if (isArray(shopList)) {
            Countly.push(shopList.join());
        }
    })()

    // 業務代碼
    todo....
}

固然除了「靜默處理」外咱們還能夠選擇「強制轉換」,將返回值轉換成咱們須要的值類型,完成邏輯的延續。

// 業務代碼入口
function main () {
    
    // 監控代碼 與業務無關
    (function () {
        var shopList = getShopNameList(); // return undefined
        Countly.push(isArray(shopList) ? shopList.join() : '');
    })()

    // 業務代碼
    todo....
}

「入參」在書寫一個函數並給外部使用的時候,對入參該如何處理反饋?

當咱們寫一個函數方法提供給除本身以外的人使用,或者是在編寫前端底層框架、UI組件,提供給外部人員使用,咱們對入參(外部使用者輸入)應該要儘量的檢查詳細。由於是給外部使用,咱們沒法知道業務場景,因此使用「靜默處理」是不合適的,咱們沒法知道靜默處理的內容與其餘業務邏輯有否有耦合,既然靜默了最終仍是會致使bugs出現,還不如直接「拋錯誤」提醒使用者。

在第三方框架中,都會自定義一個相似於warn的方法用於拋出變量檢查的不合法結果。並且爲了防止檢查代碼的增長而致使的線上代碼量的增長,一般檢查過程都會區分本地開發環境和線上生產環境。

// 代碼取自vue源碼
  if (process.env.NODE_ENV !== 'production' && isObject(def)) {
    warn(
      'Invalid default value for prop "' + key + '": ' +
      'Props with type Object/Array must use a factory function ' +
      'to return the default value.',
      vm
    )
  }

這段判斷腳本結合webpack構建生產環境的代碼時就會被刪除,不會增長生產環境的代碼量。

vue框架的組件系統中對組件傳參的行爲vue在框架層面上就支持了檢查機制。若是傳入的數據不符合規格,vue會發出警告。

Vue.component('example', {
  props: {
    // 基礎類型檢測 (`null` 意思是任何類型均可以)
    propA: Number,
    // 多種類型
    propB: [String, Number],
    // 必傳且是字符串
    propC: {
      type: String,
      required: true
    },
    // 數字,有默認值
    propD: {
      type: Number,
      default: 100
    },
    // 數組/對象的默認值應當由一個工廠函數返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定義驗證函數
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

由於咱們編寫vue組件也會提供給他人是使用,也屬於與外部交互的場景。Vue在框架層面集成了檢查功能,也方便了咱們開發者再手動檢查參數變量了。

「自產自銷」除了以上兩類與外部交互的場景,更多須要考慮的是咱們在編寫業務代碼時,「自產自銷」的變量該如何處理?

外部交互的場景,咱們對入參以及返回值具備不可控性,但對於開發者開發業務時的場景,傳參時,或者是函數返回值,都是咱們本身定義的,相對具備很強的可控性。

規定參數類型是string字符串時,咱們大機率不會傳入一個數組,並且變量的值也不會由外部環境的變化而變化(ajax返回的參數,外部接口返回的參數,類型可能會變)。

那麼剩下的狀況大部分會集中在js標量基礎類型值。

  • 規定傳入number 13,咱們傳入了string '13'
  • 規定傳入boolean true,咱們傳入了真值 '123'
  • ...

針對這種狀況,咱們對入參的值具備必定的可預期性,預期類型可能不一樣,爲了程序的健壯性,可讀性更高,更容易使協做同窗理解,咱們通常採用「強制轉換」將值轉換成咱們指望的類型。即便「強制轉換」的過程當中程序發生了報錯從而中斷,這也是在調試過程當中產生程序中斷問題,也能更好的提早暴露這個問題,避免在線上環境發生bugs。

function add(num1, num2) {
    return (toNumber(num1) + toNumber(num2))
}
add('123', '234');
  • toInteger
  • toNumber
  • toString
  • toSafeInteger
  • !!(toBoolean)

隱式強制類型轉換會踩到哪些坑?

由於js會默默的進行隱式類型轉換,因此多數坑都是發生在對值的操做過程當中發生了隱式類型轉換。

另外類型轉換越清晰,可讀性越高,更容易理解。

  • string型數字調用toFixed()方法報錯
'123'.toFixed(2) // Uncaught TypeError: "123".toFixed is not a function
  • + 法中有字符串出現則操做變成字符串拼接
function add(num1, num2) {
    return num1 + num2
}
add(123, ''); //  return string '123'
  • 當咱們使用==進行值相等判斷的時候兩邊的值會進行隱式強制類型轉換,而轉換的結果每每不盡人意。
function test(a) {
    if (a == true) { // 不推薦
        console.log('true')
    } else {
        console.log('false')        
    }
}
test('22')  // 'false'

// 緣由
'22' == true

兩邊都會發生隱式強制轉換,'22' --> 22 , true --> 1, 
所以 22 == 1  // false
function test(a) {
    if (a == '') {
        console.log('true')
    } else {
        console.log('false')        
    }
}
test(0)  // 'true'

// 緣由
0 == ''

字符串會發生隱式類型轉轉 '' --> 0
所以 0 == 0 // true

相同的場景還有

[] == 0 // true
[] == '' // true

因此當咱們進行相等判斷時涉及到[], 0, '', boolean,不該該使用==,而應該採用===,杜絕發生隱式強制類型轉換的操做。

全局環境如何作到變量的類型檢查?

依靠開發者進行參數變量的類型檢查,很是考驗js開發者的js基礎功,尤爲在團隊協做下很難作到完美的類型檢查。vue2的源碼開發使用了flow協助進行類型檢查。

Flow 是一個facebook出品靜態類型檢測工具;在現有項目中加上類型標註後,能夠在代碼階段就檢測出對變量的不恰當使用。Flow 彌補了 JavaScript 天生的類型系統缺陷。利用 Flow 進行類型檢查,可使你的項目代碼更加健壯,確保項目的其餘參與者也能夠寫出規範的代碼;而 Flow 的使用更是方便漸進式的給項目加上嚴格的類型檢測。
// @flow
function getStrLength(str: string): number{ 
    return str.length; 
}
getStrLength('Hello World');

另外還有微軟出品的TypeScript,採用這門js超集編程語言也能開發具備靜態類型的js應用。

  • TypeScript 增長了代碼的可讀性和可維護性,能夠在編譯階段就發現大部分錯誤,這總比在運行時候出錯好。
  • TypeScript 是 JavaScript 的超集,.js 文件能夠直接重命名爲 .ts 便可
  • 有必定的學習成本,須要理解接口(Interfaces)、泛型(Generics)、類(Classes)、枚舉類型

總結

本文從3個類型檢查原則「返回值」「入參」「自產自銷」爲出發點,分別闡述了這三種狀況下的處理方法「靜默處理」「拋錯誤」「強制轉換」。本文闡述的是一種思路,這三種處理方法其實在各個原則中都會使用,最重要的仍是取決於業務的需求和理解。可是儘可能的對變量類型作檢查是沒有錯的!

本文來自 二口南洋,有什麼須要討論的歡迎找我。
相關文章
相關標籤/搜索