前端數據不可信,後端數據不可知
在前端項目開發與生產的過程當中,「cannot read property of undefined」是一個常見的錯誤。從不可知獲得一個空數據問題在所不免。面對這種問題咱們該怎麼辦呢?前端
針對於特定的業務和數據,前端即便預知數據不存在,也沒法處理,任其崩潰便可。但大部分狀況下,若是由於列表某條數據出現錯誤而致使整個頁面沒法訪問,用戶將對咱們系統健壯性產生信任危機。git
爲了可以在對象( JavaScript 中數組也是對象)中安全的取值,須要驗證對象中數據項是否存在,if 語句判斷固然是沒有問題的,可是若是數據路徑較深,代碼就過於冗餘了,而常見的處理方案有以下幾種。es6
JavaScript 中咱們能夠經過 && 來實現獲取員工的地址郵編代碼github
const result = (staff.address && staff.address[0] && staff.address[0].zip) || ''
這種解析方式和 JavaScript 異於其餘語言的判斷機制有關。大部分語言都僅有 true 和 false, JavaScript 有 truthy
概念,即在某些場景下會被推斷爲 true
。後端
固然如下數據會被解析爲 false:數組
除此以外,都會被解析爲 true,即便空數組, 空對象(注: Python 空字典,空列表,空元組均在判斷中會被解析爲 false)也不例外。安全
同時 && || 不只僅返回 true 和 false,而是數據項。code
運算符 | 說明 |
---|---|
邏輯與,AND(&& ) |
若第一項可轉換爲 true ,則返回第二項;不然,返回第一項目。 |
邏輯或,OR | 若第一項可轉換爲 true ,則返回第一項;不然,返回第二項目。 |
邏輯非,NOT(! ) |
若當前項可轉換爲 true ,則返回 false ;不然,返回 true |
既然能夠經過 && 來對數據進行嗅探,那麼咱們能夠退一步,若是當前沒有項目數據,利用 || 返回空對象或者空數組。orm
const EMPTY_OBJ = {} const result = (((staff || EMPTY_OBJ).address || EMPTY_OBJ)[0] || EMPTY_OBJ).zip || ''
對比上一個方案,雖然相比上述代碼更爲複雜。 可是若是針對擁有完整數據的數據項目而言,對數據的訪問次數較少(. 的使用率),而上一個方案針對完善數據的訪問會多很多。而大部分數據無疑是正確與完整的。對象
該方法無需驗證對象中數據項是否存在,而是經過錯誤處理直接處理。
let result = '' try { result = staff.address[0].zip } catch { // 錯誤上報 }
try catch 方案更適合必要性數據缺失做爲上報的狀況。但若是發生了必要性內容數據缺失,前端界面崩潰反而是一件好事。因此 try catch 不太適合處理對象安全訪問這種問題,僅僅做爲可選方案。
上述處理方式都很痛苦,所以 ES2020 引入了「鏈判斷運算符」(optional chaining operator)?.
,簡化上面的寫法。
const reuslt = staff?.address?.[0]?.zip || ''
簡單來解釋:
a?.b // 等同於 a == null ? undefined : a.b a?.[x] // 等同於 a == null ? undefined : a[x] a?.b() // 等同於 a == null ? undefined : a.b() a?.() // 等同於 a == null ? undefined : a()
若是你想要詳細瞭解,能夠參考阮一峯 ECMAScript 6 入門 鏈判斷運算符 一篇。
某些狀況下,咱們須要傳遞路徑來動態獲取數據,如 'staff.address[0].zip', 這裏手寫了一個處理代碼。傳入對象和路徑,獲得對象,對象 key 以及 value。
/** * 根據路徑來獲取 對象內部屬性 * @param obj 對象 * @param path 路徑 a.b[1].c */ function getObjPropByPath(obj: Record<string, any>, path: string) { let tempObj = obj const keyArr = path.split('.').map(x => x.trim()) let i: number = 0 for (let len = keyArr.length; i <len - 1; ++i) { let key = keyArr[i] // 簡單判斷是不是數組數據,若是 以 ] 結尾的話 const isFormArray = key.endsWith(']') let index: number = 0 if (isFormArray) { const data = key.split('[') ?? [] key = data[0] ?? '' // 對於 parseInt('12]') => 12 index = parseInt(data[1], 10) } if (key in tempObj) { tempObj = tempObj[key] if (isFormArray && Array.isArray(tempObj)) { tempObj = tempObj[index] if (!tempObj) { return {} } } } else { return {} } } if (!tempObj) { return {} } return { o: tempObj, k: keyArr[i], v: tempObj[keyArr[i]] } }
不過筆者寫的方案較爲粗糙,但 lodash 對象模塊中也有該功能,感興趣的能夠參考其實現方式。lodash get
// 根據 object對象的path路徑獲取值。 若是解析 value 是 undefined 會以 defaultValue 取代。 // _.get(object, path, [defaultValue]) var object = { 'a': [{ 'b': { 'c': 3 } }] }; _.get(object, 'a[0].b.c'); // => 3 _.get(object, ['a', '0', 'b', 'c']); // => 3 _.get(object, 'a.b.c', 'default'); // => 'default'
固然,咱們大部分狀況下使用 || 都沒有問題,可是因爲 falsy
的存在. || 對於 false 和 undefined 是同樣的。可是某些狀況下,false 是有意義的,true, false, undefined 均表明一種含義,這時候,咱們還須要對數據進行其餘處理,使用 in 或者 hasOwnProperty 進行存在性判斷。
針對於這種狀況,ES2020 引入了一個新的 Null 判斷運算符 ??
。它的行爲相似||
,可是隻有運算符左側的值爲 null
或 undefined
時,纔會返回右側的值。如
const result = staff.address && staff.address[0] && staff.address[0].zip ?? ''
若是你以爲這篇文章不錯,但願能夠給與我一些鼓勵,在個人 github 博客下幫忙 star 一下。
博客地址