摘要:一返回值調用外部方法獲取的值需要對類型做判斷,因為我們對方法返回的值是有期望值類型,但是卻不能保證這個接口返回的值一直是同一個類型。
19年目標:消滅英語!我新開了一個公眾號記錄一個程序員學英語的歷程
有提升英語訴求的小伙伴可以關注公眾號:csenglish 程序員學英語,每天花10分鐘交作業,跟我一起學英語吧
javascript作為一門動態類型語言,具有很高的動態靈活性,當定義函數時,傳入的參數可以是任意類型。但我們在實際編寫函數邏輯時默認是對參數有一定要求的。這也容易導致預期參數與實際參數不符的情況,從而導致bug的出現。本文在這個層面探討javascript檢查參數的必要性。為什么要進行類型檢查?
從兩點常見的場景來看這個問題:
程序中期望得到的值與實際得到的值類型不相符,在對值進行操作的時候程序報錯,導致程序中斷。
舉個我們最常見的調用服務端ajax請求取到返回值進行操作的例子:
ajax("/getContent", function (json) { // json的返回數據形式 // {data: 18} var strPrice = (data.data).toFixed(2); })
如果服務端返回的數據形式以及返回的data一定是number類型,我們這樣操作肯定沒有問題。
但是如果服務端返回的數據發生了變化,返回給我們的形式變成了:
{ data: "18.00" }
而我們在js中并沒有對變量做檢測,就會導致程序報錯。
"18.00".toFixed(2) // Uncaught TypeError: "18.00".toFixed is not a function
跟第一點相似也是期望得到的值與實際得到的值類型不相符,但是對值操作不會報錯,js利用隱式類型轉換得到了我們不希望得到的值,這種情況會加大我們對bug的追蹤難度。
舉一個也是比較常見的例子:
/** * input1 [number] * input2 [number] * return [number] **/ function sumInput (input1, input2) { return input1 + input2; }
sumInput方法的兩個入參值可能來自外界用戶輸入,我們無法保證這是一個正確的number類型值。
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,我們需要在一些關鍵點進行檢查,而關鍵點更多的是由業務決定的,并沒有一個統一的原則指導我們哪里必須進行類型判斷。
但大體趨勢上可以參考以下我總結的幾點意見。
一、「返回值」調用外部方法獲取的值需要對類型做判斷,因為我們對方法返回的值是有期望值類型,但是卻不能保證這個接口返回的值一直是同一個類型。換個意思講就是我們對我們不能保證的,來源于外部的值都要保持一顆敬畏之心。這個值可能來自第三方工具函數的返回值,或者來自服務端接口的返回值,也可能是另一位同事寫的抽離公共方法。
二、「入參」在書寫一個函數并給外部使用的時候,需要對入參做較嚴格的類型判斷。這里強調的也是給外部使用的場景,我們在函數內部會對入參做很多邏輯上的處理,如果不對入參做判斷,我們無法確保外部使用者傳入的到底是什么類型的參數。
三、「自產自銷」除了以上兩類與外部交互的場景,更多需要考慮的是我們在編寫業務代碼時,“自產自銷”的變量該如何處理。解釋一下“自產自銷”的意思,在編寫業務代碼時,我們會根據業務場景定義很多函數,以及會調用函數取返回值。在這個過程中會有入參的情況,而這些參數完全是自己編寫自己使用,在這種對代碼相對了解的前提下無條件的進行變量類型判斷無疑會增加編碼的復雜度。
在實際編碼中我們更多的會使用強制類型轉換[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,不應該使用==,而應該采用===,杜絕發生隱式強制類型轉換的操作。
全局環境如何做到變量的類型檢查?依靠開發者進行參數變量的類型檢查,非??简瀓s開發者的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個類型檢查原則「返回值」「入參」「自產自銷」為出發點,分別闡述了這三種情況下的處理方法「靜默處理」「拋錯誤」「強制轉換」。本文闡述的是一種思路,這三種處理方法其實在各個原則中都會使用,最重要的還是取決于業務的需求和理解。但是盡量的對變量類型做檢查是沒有錯的!
本文來自二口南洋,有什么需要討論的歡迎找我。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/82493.html
摘要:因此,當聲明一個變量但還未賦值時,它將被賦予值。和之間唯一真正的關系是它們在類型強制過程中都判斷為。之所以所以是因為沒有執行嚴格的比較,因為在比較類型時使用更嚴格。 原文:http://davidshariff.com/blog/...翻譯:瘋狂的技術宅 本文首發微信公眾號:jingchengyideng歡迎關注,每天都給你推送新鮮的前端技術文章 Undefined 這個概念聽起來...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:它有時被稱做鴨式辨型法或結構性子類型化。在里,接口的作用就是為這些類型命名和為你的代碼或第三方代碼定義契約。賦值后,和再也不能被改變了。函數的返回值類型是通過其返回值推斷出來的此例是和。技術本身沒有好壞,長遠看,弱類型語言并不是那么的友好。 showImg(https://segmentfault.com/img/bVbwQe2?w=1792&h=1266); TypeScript不...
閱讀 1740·2021-11-25 09:43
閱讀 1785·2021-11-24 10:41
閱讀 3105·2021-09-27 13:36
閱讀 811·2019-08-30 15:53
閱讀 3567·2019-08-30 15:44
閱讀 866·2019-08-30 14:03
閱讀 2572·2019-08-29 16:38
閱讀 996·2019-08-29 13:23