Js获取Object深层对象 Js中安全获取Object深层对象的方法实例
爲爲懿笑 人气:0前言
做前端的小伙伴一定遇到过后端返回的数据有多层嵌套的情况,当我要获取深层对象的值时为防止出错,会做层层非空校验,比如:
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 取代。
参数
- object (Object) : 要检索的对象。
- path (Array|string) : 要获取属性的路径。
- [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, 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
参考资料:
总结
加载全部内容