Preface
Friends who work in front-end must have encountered the situation where the data returned by the back-end is nested with multiple layers. When I want to get the value of the deep object, I will do layers of non-empty verification to prevent errors, such as:
const obj = { goods: { name: 'a', tags: { name: 'fast', id: 1, tagType: { name: 'Label' } } } }
When I need to get it, I judge it like this
if ( !== null && !== null && !== null) { }
If the attribute name is verbose, you can't read this code.
Of course, ECMAScript 2020 has been launched?. to solve this problem:
let name = obj?.goods?.tags?.tageType?.name;
But how to deal with it in browsers that are incompatible with ES2020?
text
Students who have used lodash may know that there is a get method in lodash, and the official website says this:
_.get(object, path, [defaultValue])
Get the value based on the path path of the object object. If parsing value is undefined, it will be replaced by defaultValue.
parameter
- object (Object) : The object to retrieve.
- path (Array|string) : The path to get the property.
- [defaultValue] ()* : If the parsed value is undefined , this value will be returned.
example
var object = { 'a': [{ 'b': { 'c': 3 } }] }; _.get(object, 'a[0].'); // => 3 _.get(object, ['a', '0', 'b', 'c']); // => 3 _.get(object, '', 'default'); // => 'default'
This problem is solved, but (I'm afraid of "but")
What if I can't use the introduction of the lodash library due to various reasons such as project, company requirements, etc.?
With that, let's see how lodash is implemented. Isn't it enough to just extract the code? In this way, we can move bricks happily again~~
Lodash implementation:
function get(object, path, defaultValue) { const result = object == null ? undefined : baseGet(object, path) return result === undefined ? defaultValue : result }
What we do here is very simple. Let’s look at the return first. If object returns the default value and the core code is in baseGet, then let’s look at the implementation of baseGet
function baseGet(object, path) { // Convert the input string path into an array. path = castPath(path, object) let index = 0 const length = // traverse the array to get each layer of objects while (object != null && index < length) { object = object[toKey(path[index++])] // toKey method } return (index && index == length) ? object : undefined }
Here we use two functions castPath (convert the input path to an array) and toKey (convert the real key)
tokey function:
/** 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 }
There are two main things here,
- If the key type is String or symbol, it will be returned directly
- If the key is another type, then convert to String to return
Here I also use the isSymbol function to determine whether it is Symbol type, so the code will not be posted. Interested students can check the lodash source code
castPath function:
import isKey from './' import stringToPath from './' /** * 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 ((value)) { return value } return isKey(value, object) ? [value] : stringToPath(value) }
castPath mainly converts the input path into an array, preparing for the subsequent traversal to obtain deep objects.
IsKey() and stringToPath() are used here.
isKey is simple to determine whether the current value is the key of the object.
stringToPath mainly deals with the situation where the input path is a string, such as: '[0].d'
stringToPath function:
import memoizeCapped from './' 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 ((0) === charCodeOfDot) { ('') } (rePropName, (match, expression, quote, subString) => { let key = match if (quote) { key = (reEscapeChar, '$1') } else if (expression) { key = () } (key) }) return result })
Here we mainly exclude the . and [] from the path, parse out the real key and add it to the array.
memoizeCapped function:
import memoize from '../' /** 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 ( === MAX_MEMOIZE_SIZE) { () } return key }) return result } export default memoizeCapped
Here is a limit on the cache key. Clear the cache when it reaches 500.
memoize function:
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 ? (this, args) : args[0] const cache = if ((key)) { return (key) } const result = (this, args) = (key, result) || cache return result } = new ( || Map) return memoized } = Map
In fact, I didn’t understand the last two functions very well. If the input path is '', wouldn’t it be possible to convert it into an array directly? Why use closures for caching?
I hope the guy who understands it can give you an answer
Since the source code uses many functions and in different files, I have simplified them.
The complete code is as follows:
/** * 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].') * // => 3 * * get(object, ['a', '0', 'b', 'c']) * // => 3 * * get(object, '', 'default') * // => 'default' */ safeGet (object, path, defaultValue) { let result if (object != null) { if (!(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 ((0) === '.'.charCodeAt(0)) { ('') } const rePropName = RegExp( // Match anything that isn't a dot or bracket. '[^.[\]]+|\[(?:([^"'][^[]*)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))' , 'g') (rePropName, (match, expression, quote, subString) => { let key = match if (quote) { key = (/\(\)?/g, '$1') } else if (expression) { key = () } (key) }) path = result } } let index = 0 const 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 }
The code is borrowed from lodash
References:
- lodash official documentation
- Github
Summarize
This is the article about safely obtaining Object deep objects in Js. This is all about this. For more related contents of obtaining Object deep objects in Js, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!