1. 程式人生 > 程式設計 >Js中安全獲取Object深層物件的方法例項

Js中安全獲取Object深層物件的方法例項

目錄
  • 前言
  • 正文
    • 引數
    • 例子
    • lodash的實現:
    • tokey函式:
    • castPath函式:
    • stringToPath函式:
    • memoizeCapped函式:
    • memoize函式:
    • 完整程式碼如下:
  • 參考資料:
    • 總結

      前言

      做前端的小夥伴一定遇到過後端返回的資料有多層巢狀的情況,當我要獲取深層物件的值時為防止出錯,會做層層非空校驗,比如:

      const obj = {
        goods: {
          name: 'a',tags: {
            name: '快速',id: 1,tagType: {
              name: '標籤'
             }
           }
         }
      }
      

      當我需要獲取tagType.name時判斷是這樣的

      if (obj.goods !== null
          && obj.goods.tags !== null
          && obj.goods.tags.tagType !== null) {
       
      }
      

      如果屬性名冗長,這斷程式碼就沒法看。

      當然ECMAScript2020中已經推出了?.來解決這個問題:

      let name = obj?.goods?.tags?.tageType?.name;
      

      但是不相容ES2020的瀏覽器中怎麼處理呢?

      正文

      使用過lodash的同學可能知道,lodash中有個get方法,官網是這麼說的:

      _.get(object,path,[defaultValue])
      

      根據 object物件的path路徑獲取值。 如果解析 value 是 undefined 會以 defaultValue 取代。

      引數

      1. object (Object) : 要檢索的物件。
      2. path (Array|string) : 要獲取屬性的路徑。
      3. [defaultValue] ()* : 如果解析值是 undefined ,這值會被返回。

      例子

      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'
      

      如此問題解決,但是(就怕有“但是”)

      如果因為專案、公司要求等各種原因我不能使用引入lodash庫怎麼辦呢?

      有了,我們看看lodash是怎麼實現的,把程式碼摘出來不就可以了嗎,如此又能快樂的搬磚了~~

      lodash的實現:

      function get(object,defaultValue) {
       const result = object == null ? undefined : baseGet(object,path)
       return result === undefined ? defaultValue : result
      }
      

      這裡做的事很簡單,先看返回,如果object即返回預設值,核心程式碼在baseGet中,那我們再來看baseGet的實現

      function baseGet(object,path) {
       // 將輸入的字串路徑轉換成陣列,
       path = castPath(path,object)
      ​
       let index = 0
       const length = path.length
       // 遍歷陣列獲取每一層物件
       while (object != null && index < length) {
        object = object[toKey(path[index++])] // toKey方法
        }
       return (index && index == length) ? object : undefined
      }
      ​
      

      這裡又用到兩個函式castPath(將輸入路徑轉換為陣列)、toKey(轉換真實key)

      tokey函式:

      /** Used as references for various `Number` constants. */
      const INFINITY = 1 / 0
      ​
      /**
       * Converts `value` to a string key if it's not a string or symbol.
       *
       * @private
       * @param {*} value The value to inspect.
       * @returns {string|symbol} Returns the key.
       */
      function toKey(value) {
       if (typeof value === 'string' || isSymbol(value)) {
        return value
        }
       const result = `${value}`
       return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result
      }
      

      這裡主要做了兩件事,

      • 如果key型別為String或者symbol則直接返回
      • 如果key為其他型別,則轉換為String返回

      這裡還用到了isSymbol函式來判斷是否為Symbol型別,程式碼就不貼了,感興趣的同學可以檢視lodash原始碼

      castPath函式:

      import isKey from './isKey.'
      import stringToPath from './stringToPath.js'
      ​
      /**
       * Casts `value` to a path array if it's not one.
       *
       * @private
       * @param {*} value The value to inspect.
       * @param {Object} [object] The object to query keys on.
       * @returns {Array} Returns the cast property path array.
       */
      function castPath(value,object) {
       if (Array.isArray(value)) {
        return value
        }
       return isKey(value,object) ? [value] : stringToPath(value)
      }
      ​
      

      castPath主要是將輸入的路徑轉換為陣列的,為後面遍歷獲取深層物件做準備。

      這裡有用到了isKey()與stringToPath().

      isKey比較簡單判斷當前value是否為object的key。

      stringToPath主要處理輸入路徑為字串的情況,比如:'a.b.c[0].d'

      stringToPath函式:

      import memoizeCapped from './memoizeCapped.js'
      ​
      const charCodeOfDot = '.'.charCodeAt(0)
      const reEscapeChar = /\(\)?/g
      const rePropName = RegExp(
       // Match anything that isn't a dot or bracket.
       '[^.[\]]+' + '|' +
       // Or match property names within brackets.
       '\[(?:' +
        // Match a non-string expression.
        '([^"'][^[]*)' + '|' +
        // Or match strings (supports escaping characters).
        '(["'])((?:(?!\2)[^\\]|\\.)*?)\2' +
       ')\]'+ '|' +
       // Or match "" as the space between consecutive dots or empty brackets.
       '(?=(?:\.|\[\])(?:\.|\[\]|$))','g')
      ​
      /**
       * Converts `string` to a property path array.
       *
       * @private
       * @param {string} string The string to convert.
       * @returns {Array} Returns the property path array.
       */
      const stringToPath = memoizeCapped((string) => {
       const result = []
       if (string.charCodeAt(0) === charCodeOfDot) {
        result.push('')
        }
       string.replace(rePropName,(match,expression,quote,subString) => {
        let key = match
        if (quote) {
         key = subString.replace(reEscapeChar,'$1')
         }
        else if (expression) {
         key = expression.trim()
         }
        result.push(key)http://www.cppcns.com
        })
       return result
      })
      ​
      

      這裡主要是排除路徑中的 . 與[],解析出真實的key加入到陣列中

      memoizeCapped函式:

      import memoize from '../memoize.js'
      ​
      /** Used as the maximum memoize cache size. */
      const MAX_MEMOIZE_SIZE = 500
      ​
      /**
       * A specialized version of `memoize` which clears the memoized function's
       * cache when it exceeds `MAX_MEMOIZE_SIZE`.
       *
       * @private
       * @param {Function} func The function to have its output memoized.
       * @returns {Function} Returns the new memoized function.
       */
      function memoizeCapped(func) {
       const result = memoize(func,(key) => {
        const { cache } = result
        if (cache.size === MAX_MEMOIZE_SIZE) {
         cache.clear()
         }
        return key
        })
      ​
       return result
      }
      ​
      export default memoizeCapped
      ​
      

      這裡是對快取的www.cppcns.comkey做一個限制,達到500時清空快取

      memoize函式:

      function memoize(func,resolver) {
       if (typeof func !== 'function' || (resolver != null && typeof resolver !== 'function')) {
        throw new TypeError('Expected a function')
        }
       const memoized = function(...args) {
        const key = resolver ? resolver.apply(this,args) : args[0]
        const cache = memoized.cache
      ​
        if (cache.has(key)) {
         return cache.get(key)
         }
        const result = func.apply(this,args)
        memoized.cache = cache.set(key,result) || cache
        return result
        }
       memoized.cache = new (memoize.Cache || Map)
       return memoized
      }
      ​
      memoize.Cache = Map
      

      其實最後兩個函式沒太看懂,如果輸入的路徑是'a.b.c',那直接將它轉換成陣列不就可以嗎?為什麼要用到閉包進行快取。

      希望看懂的大佬能給解答一下

      由於原始碼用到的函式較多,且在不同檔案,我將他們進行了精簡,

      完整程式碼如下:

      /**
        * Gets the value at `path` of `object`. If the resolved value is
        * `undefined`,the `defaultValue` is returned in its place.
        * @example
        * const object = { 'a': [{ 'b': { 'c': 3 } }] }
        *
        * get(object,'a[0].b.c')
        * // => 3
        *
        * get(object,'c'])
        * // => 3
        *
        * get(object,'default')
        * // => 'default'
        */
       safeGet (object,defaultValue) {
        let result
        if (object != null) {
         if (!Array.isArray(path)) {
          const type = typeof path
          if (type === 'number' || type === 'boolean' || path == null ||
          /^\w*$/.test(path) || !(/.|[(?:[^[]]*|(["'])(?:(?!\1)[^\]|\.)*?\1)]/.test(path)) ||
           (object != null && path in Object(object))) {
           path = [path]
           } else {
           const result = []
           if (path.charCodeAt(0) === '.'.charCodeAt(0)) {
            result.push('')
            }
           const rePropName = RegExp(
            // Match anything that isn't a dot or bracket.
            '[^.[\]]+|\[(?:([^"'][^[]*)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))','g')
           path.replace(rePropName,subString) => {
            let key = match
            if (quote) {
             key = subString.replace(/\(\)?/g,'$1')
             } else if (expression) {
             key = expression.trim()
             }
            result.push(key)
            })
           path = result
           }
          }
         let index = 0
         const length = path.length
         const toKey = (value) => {
          if (typeof value === 'string') {
           return value
           }
          const result = `${value}`
          return (result === '0' && (1 / value) === -(1 / 0)) ? '-0' : result
          }
         while (object != null && index < length) {
          object = object[toKey(path[index++])]
          }
         result = (index && index === length) ? object : undefined
         }
        return result === undefined ? defaultValue : result
        }
      

      程式碼借鑑自lodash

      參考資料:

      • lodash官方文件

      總結

      到此這篇關於Js中安全獲取Object深層物件的文章就介紹到這了,更多相關Js獲取Object深層物件內容請搜尋我們以前的文章或繼續瀏覽下面的www.cppcns.com相關文章希望大家以後多多支援我們!