Js中安全获取Object深层对象的方法实例

编辑: admin 分类: javascript 发布时间: 2022-01-01 来源:互联网
目录
  • 前言
  • 正文
    • 参数
    • 例子
    • 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, path, 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.js'
      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)
        })
        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
      ​
      

      这里是对缓存的key做一个限制,达到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, 【来源:美国cn2服务器 转载请说明出处】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, ['a', '0', 'b', 'c'])
         * // => 3
         *
         * get(object, 'a.b.c', 'default')
         * // => 'default'
         */
        safeGet (object, path, 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, (match, expression, quote, 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官方文档
      • Github

      总结

      到此这篇关于Js中安全获取Object深层对象的文章就介绍到这了,更多相关Js获取Object深层对象内容请搜索hwidc以前的文章或继续浏览下面的相关文章希望大家以后多多支持hwidc!

      【本文来源:迪拜服务器 转载请说明出处】