Js中安全獲取Object深層對象的方法實例
前言
做前端的小夥伴一定遇到過後端返回的數據有多層嵌套的情況,當我要獲取深層對象的值時為防止出錯,會做層層非空校驗,比如:
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
參考資料:
- lodash官方文檔
- Github
總結
到此這篇關於Js中安全獲取Object深層對象的文章就介紹到這瞭,更多相關Js獲取Object深層對象內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- JavaScript Reduce使用詳解
- webpack源碼中一些精妙的方法總結
- lodash裡的toLength和toPairs方法詳解
- JavaScript深拷貝的幾種實現方法實例
- JS實現數組過濾從簡單到多條件篩選