總結對象安全訪問處理方案

前端數據不可信,後端數據不可知

在前端項目開發與生產的過程當中,「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:數組

  • null
  • undefined
  • NaN
  • 0
  • 空字符串

除此以外,都會被解析爲 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 || ''

對比上一個方案,雖然相比上述代碼更爲複雜。 可是若是針對擁有完整數據的數據項目而言,對數據的訪問次數較少(. 的使用率),而上一個方案針對完善數據的訪問會多很多。而大部分數據無疑是正確與完整的。對象

try catch

該方法無需驗證對象中數據項是否存在,而是經過錯誤處理直接處理。

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'

其餘

Null 判斷運算符

固然,咱們大部分狀況下使用 || 都沒有問題,可是因爲 falsy 的存在. || 對於 false 和 undefined 是同樣的。可是某些狀況下,false 是有意義的,true, false, undefined 均表明一種含義,這時候,咱們還須要對數據進行其餘處理,使用 in 或者 hasOwnProperty 進行存在性判斷。

針對於這種狀況,ES2020 引入了一個新的 Null 判斷運算符 ??。它的行爲相似||,可是隻有運算符左側的值爲 nullundefined 時,纔會返回右側的值。如

const result = staff.address && staff.address[0] && staff.address[0].zip ?? ''

鼓勵一下

若是你以爲這篇文章不錯,但願能夠給與我一些鼓勵,在個人 github 博客下幫忙 star 一下。
博客地址

參考資料

ES6 入門教程

相關文章
相關標籤/搜索