JS 可選鏈操作符?. 空值合並運算符?? 詳解,更精簡的安全取值與默認值設置小技巧


壹 ❀ 引

說來也比較慚愧,可選鏈操作符?.在公司項目中使用特別頻繁,而我在之前一直以為是類似奇技淫巧的寫法,所以也沒去查閱相關文檔,直到在學習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

Nullish Coalescing for JavaScript

可選鏈操作符 - JavaScript | MDN

空值合並運算符 - JavaScript | MDN


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM