您当前的位置:首页 > 网站建设 > javascript
| php | asp | css | H5 | javascript | Mysql | Dreamweaver | Delphi | 网站维护 | 帝国cms | React | 考试系统 | ajax |

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

51自学网 2022-05-02 21:32:49
  javascript

前言

做前端的小伙伴一定遇到过后端返回的数据有多层嵌套的情况,当我要获取深层对象的值时为防止出错,会做层层非空校验,比如:

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 = //(/)?/gconst 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, 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

参考资料:

总结

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


JavaScript中各种二进制对象关系的深入讲解
在JS中如何使用css变量详解
51自学网,即我要自学网,自学EXCEL、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。
京ICP备13026421号-1