Js中安全獲取Object深層物件的方法例項
目錄
- 前言
- 正文
- 引數
- 例子
- 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 取代。
引數
- 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,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.' 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)http://www.cppcns.com
})
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
這裡是對快取的www.cppcns.comkey做一個限制,達到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,'c']) * // => 3 * * get(object,'default') * // => 'default' */ safeGet (object,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,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官方文件
總結
到此這篇關於Js中安全獲取Object深層物件的文章就介紹到這了,更多相關Js獲取Object深層物件內容請搜尋我們以前的文章或繼續瀏覽下面的www.cppcns.com相關文章希望大家以後多多支援我們!