壹 ❀ 引
說來也比較慚愧,可選鏈操作符?.在公司項目中使用特別頻繁,而我在之前一直以為是類似奇技淫巧的寫法,所以也沒去查閱相關文檔,直到在學習typescript時碰巧看到了可選鏈操作符與空值合並操作符兩個概念,才知道原來是ECMAScript官方提出的新語法。因為本人覺得確實非常好用,所以這里做個簡單科普,本文開始。
貳 ❀ 可選鏈操作符?.
貳 ❀ 壹 基本用法
在接近一年的各類bug修復工作中,我發現安全取值問題是bug產生的高頻原因之一,或許大家對於數據來源過於自信(或者粗心大意),認為這份數據一定符合代碼預期,它一定是個數組或者它一個是個對象,然而在某種未知情況下,你用於遍歷的數組報錯了,用於獲取屬性的對象也報錯了,恭喜你,不幸解鎖成就Uncaught TypeError: Cannot read property 'XX' of null。
因此在一次公司前端月會上,我提了一個小意見,凡是數據來源帶有查找性質,建議做安全取值判斷,比如:
// arr來源是通過其它查找行為所生成,可能是數組也可能是null
arr && arr.forEach((e)=>console.log(e))
// obj來源也是查找,可能是null
let name = obj && obj.name
// fn通過props傳遞,可能沒傳
fn && fn(...rest);
上述代碼應該特別好理解,對於a && b來說,只有a為真時邏輯才會走到b,上述邏輯等同於:
if(obj){
let name = obj.name;
};
而可選鏈操作符?.的出現會進一步簡化我們的寫法,而且可讀性更加友好,比如:
arr?.forEach((e)=>console.log(e));
let name = obj?.name;
fn?.(...rest);
事實上,我們在開發中常常遇到屬性多層級的獲取場景,比如下面這樣的一個對象,我們需要獲取name字段:
let o = {
user:{
name:'echo'
}
};
// 保險做法
let name = o.user && o.user.name;
// 換成?.
let name = o.user?.name;
你看,原本臃腫的判斷邏輯我們現在直接簡化成了一小段,?的含義也很好理解,o.user是真值嗎?是那就通過.獲取name字段。
貳 ❀ 更強的代碼健壯性
?.除了簡化代碼量提升語義化外,其實更重要的一點是提升了代碼的健壯性,一定程度上減少了安全取值類的低級錯誤,比如一個對象的值意外的是null或者undefined,我們在獲取name時:
let o = null;//或者undefined都行
// 犯錯的寫法
let name = o.name;// 報錯,告訴你不能從null上讀取name屬性
// 換成?.
let name = o?.name;
console.log(name);//undefined
當換成?.這段代碼並不會報錯,而且當我們訪問name時就像單純聲明了一個name變量未賦值,它輸出undefined。
同樣,當我們希望將一個方法(此方法執行后還會返回一個值)作為props傳遞給下層組件調用時,可能有這兩種場景:
let fn = function(a){
return a;
};
// 假設傳遞了
let a = fn?.(1);
console.log(a);//1
// 假設沒傳
let func = undefined;
let b = func?.(1);
console.log(b);// undefined
你會發現使用?.調用一個可能傳遞的方法也非常簡單,而假設這個方法沒傳遞,函數的返回值也是undefined,這一點與上面對象屬性獲取的例子結果保持一致,總結來說,當引用為空(null或者undefined)的情況下不會引起錯誤,且表達式短路返回值是 undefined。
叄 ❀ 四種常用語法
?.支持如下幾種語法:
obj?.prop//讀取對象某個屬性
obj?.[expr]//讀取某個屬性,且這個屬性名還是個變量
arr?.[index]//讀取數組下標
fn?.(args)// 調用一個可能沒傳遞函數
我們來給幾個例子感受下:
// 讀取某個對象的屬性
let echo = {name:'聽風是風'};
let name = echo?.name;
// 讀取某個對象的屬性,屬性是個變量
let n = 'name';
let name1 = echo?.[n];
// 讀取數組下標
let arr = ['echo', '時間跳躍', '聽風是風'];
let name2 = arr?.[2];
// 執行某個可能不存在的函數
let fn = a => a;
let name3 = fn?.('聽風是風');
另外,官方給了一個比較特殊的例子,猜猜此時a是多少?
let a = 1?.3:2;
a//?
我們知道,常規小數比如0.1可以簡寫成.1,其實上述代碼格式化一下就是一個三元運算符:
let a = 1 ? .3 : 2;
由於1為真,所以a最終復制是0.3,專門提出這個例子是想告訴大家不要對於此場景有所疑惑,javascript還是能夠正確解析的,當然,為了可讀性,大家也可以通過圓括號進行范圍划分:
let a = 1?(.3):2;
叄 ❀ 空值合並運算符??
叄 ❀ 壹 概念與用法
在日常聊天中,對於表示驚嘆,無語,反問,大家可能會回復多個問號作為萬能的答復,比如:
而在javascript世界中,它的含義不僅限於如此,在ECMAScript2020中除了推出可選鏈操作符外還有一個也非常好用的空值合並運算符,它就是用兩個英文??表示,概念上也非常簡單,當一個表達式左側的操作數為 null 或者 undefined 時,返回其右側操作數,否則返回左側操作數。
我不知道大家有沒有寫過如下類似的代碼:
let user = {};
let name = user.name || 'echo';
這段代碼的本意的是,當user.name不存在時,我們期望給name設置一個默認值,便於后續程序能正常運行。但實際開發中往往會遇到如下類似的情況,比如:
// 用戶傳遞的就是0
let num = 0;
let num_ = num || 1;
// 用戶傳遞的就是空字符串
let str = '';
let str_ = str || 'echo';
以上就是兩種比較常見的場景,用戶期望就是傳遞一個數字0或者一個空字符,但對於短路邏輯||而言左側是一個假值,這會導致最終復制會以右邊為准,從而產生了一個bug。
而空值合並運算符??正是用於解決這一問題場景,讓我換個寫法:
// 用戶傳遞的就是0
let num = 0;
let num_ = num ?? 1;
// 用戶傳遞的就是空字符串
let str = '';
let str_ = str ?? 'echo';
console.log(num_, str_);// 0 ''
你看,只有當左側為undefined或者null時,右邊的值才會生效。
貳 ❀ 注意事項
需要注意的是,空值合並運算符不能與&&或者||直接共用,如下寫法都是錯誤的:
null || undefined ?? "echo";// 報錯
true && undefined ?? "echo";// 報錯
對於javascript而言,它根本不知道應該怎么讀取這段代碼,比較好的做法還是通過圓括號進行范圍划分,這樣解析器才知道那一小塊是一個整體,比如:
let name = (null || undefined) ?? "echo";
let age = (true && undefined) ?? 28;//28
OK,空值合並運算符相對而言簡單不少,那么就介紹到這里。
PS:由於ECMAScript2020概念相對來說還比較新,部分瀏覽器低版本並不支持,請確認項目中有使用babel做類似ES5轉義,不然大概率會出現瀏覽器不兼容的語法報錯,那么到這里兩個JS小技巧介紹完畢,本文結束。
參考
Optional Chaining for JavaScript
