Js中安全獲取Object深層對(duì)象的方法實(shí)例
前言
做前端的小伙伴一定遇到過(guò)后端返回的數(shù)據(jù)有多層嵌套的情況,當(dāng)我要獲取深層對(duì)象的值時(shí)為防止出錯(cuò),會(huì)做層層非空校驗(yàn),比如:
const obj = {
goods: {
name: 'a',
tags: {
name: '快速',
id: 1,
tagType: {
name: '標(biāo)簽'
}
}
}
}
當(dāng)我需要獲取tagType.name時(shí)判斷是這樣的
if (obj.goods !== null
&& obj.goods.tags !== null
&& obj.goods.tags.tagType !== null) {
}
如果屬性名冗長(zhǎng),這斷代碼就沒(méi)法看。
當(dāng)然ECMAScript2020中已經(jīng)推出了?.來(lái)解決這個(gè)問(wèn)題:
let name = obj?.goods?.tags?.tageType?.name;
但是不兼容ES2020的瀏覽器中怎么處理呢?
正文
使用過(guò)lodash的同學(xué)可能知道,lodash中有個(gè)get方法,官網(wǎng)是這么說(shuō)的:
_.get(object, path, [defaultValue])
根據(jù) object對(duì)象的path路徑獲取值。 如果解析 value 是 undefined 會(huì)以 defaultValue 取代。
參數(shù)
- object (Object) : 要檢索的對(duì)象。
- path (Array|string) : 要獲取屬性的路徑。
- [defaultValue] ()* : 如果解析值是 undefined ,這值會(huì)被返回。
例子
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'
如此問(wèn)題解決,但是(就怕有“但是”)
如果因?yàn)轫?xiàng)目、公司要求等各種原因我不能使用引入lodash庫(kù)怎么辦呢?
有了,我們看看lodash是怎么實(shí)現(xiàn)的,把代碼摘出來(lái)不就可以了嗎,如此又能快樂(lè)的搬磚了~~
lodash的實(shí)現(xiàn):
function get(object, path, defaultValue) {
const result = object == null ? undefined : baseGet(object, path)
return result === undefined ? defaultValue : result
}
這里做的事很簡(jiǎn)單,先看返回,如果object即返回默認(rèn)值,核心代碼在baseGet中,那我們?cè)賮?lái)看baseGet的實(shí)現(xiàn)
function baseGet(object, path) {
// 將輸入的字符串路徑轉(zhuǎn)換成數(shù)組,
path = castPath(path, object)
let index = 0
const length = path.length
// 遍歷數(shù)組獲取每一層對(duì)象
while (object != null && index < length) {
object = object[toKey(path[index++])] // toKey方法
}
return (index && index == length) ? object : undefined
}
這里又用到兩個(gè)函數(shù)castPath(將輸入路徑轉(zhuǎn)換為數(shù)組)、toKey(轉(zhuǎn)換真實(shí)key)
tokey函數(shù):
/** 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為其他類型,則轉(zhuǎn)換為String返回
這里還用到了isSymbol函數(shù)來(lái)判斷是否為Symbol類型,代碼就不貼了,感興趣的同學(xué)可以查看lodash源碼
castPath函數(shù):
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主要是將輸入的路徑轉(zhuǎn)換為數(shù)組的,為后面遍歷獲取深層對(duì)象做準(zhǔn)備。
這里有用到了isKey()與stringToPath().
isKey比較簡(jiǎn)單判斷當(dāng)前value是否為object的key。
stringToPath主要處理輸入路徑為字符串的情況,比如:'a.b.c[0].d'
stringToPath函數(shù):
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
})
這里主要是排除路徑中的 . 與[],解析出真實(shí)的key加入到數(shù)組中
memoizeCapped函數(shù):
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
這里是對(duì)緩存的key做一個(gè)限制,達(dá)到500時(shí)清空緩存
memoize函數(shù):
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
其實(shí)最后兩個(gè)函數(shù)沒(méi)太看懂,如果輸入的路徑是'a.b.c',那直接將它轉(zhuǎn)換成數(shù)組不就可以嗎?為什么要用到閉包進(jìn)行緩存。
希望看懂的大佬能給解答一下
由于源碼用到的函數(shù)較多,且在不同文件,我將他們進(jìn)行了精簡(jiǎn),
完整代碼如下:
/**
* 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
參考資料:
總結(jié)
到此這篇關(guān)于Js中安全獲取Object深層對(duì)象的文章就介紹到這了,更多相關(guān)Js獲取Object深層對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS對(duì)象與json字符串相互轉(zhuǎn)換實(shí)現(xiàn)方法示例
這篇文章主要介紹了JS對(duì)象與json字符串相互轉(zhuǎn)換實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了js對(duì)象與json字符串相互轉(zhuǎn)換的相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2018-06-06
JavaScript中你不知道的數(shù)學(xué)方法分享(非常實(shí)用)
JavaScript的Math對(duì)象包含了一些非常有用和強(qiáng)大的數(shù)學(xué)操作,可以在Web開(kāi)發(fā)中使用,本文為大家整理了一些非常實(shí)用的數(shù)學(xué)方法,希望對(duì)大家有所幫助2023-07-07
原生js jquery ajax請(qǐng)求以及jsonp的調(diào)用方法
下面小編就為大家?guī)?lái)一篇原生js jquery ajax請(qǐng)求以及jsonp的調(diào)用方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-08-08
JavaScript類的繼承方法小結(jié)【組合繼承分析】
這篇文章主要介紹了JavaScript類的繼承方法,結(jié)合實(shí)例形式總結(jié)分析了JavaScript繼承的概念、原理及組合繼承相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-07-07
javascript smipleChart 簡(jiǎn)單圖標(biāo)類
支持 線性圖 區(qū)域圖 柱狀圖 餅圖 支持多瀏覽器 用到的是svg vml 之后加上 多層餅圖 分段圖 和組合圖2011-01-01
基于JavaScript實(shí)現(xiàn)文件秒傳功能
在互聯(lián)網(wǎng)高速發(fā)展的今天,文件上傳已經(jīng)成為網(wǎng)頁(yè)應(yīng)用中的一個(gè)基本功能,隨著用戶上傳文件尺寸的不斷增大、對(duì)質(zhì)量清晰度的要求也越來(lái)越高,所以本文給大家介紹了如何使用JavaScript實(shí)現(xiàn)文件秒傳功能,需要的朋友可以參考下2024-01-01
js 獲取class的元素的方法 以及創(chuàng)建方法getElementsByClassName
js 獲取class的元素的方法 以及創(chuàng)建方法getElementsByClassName,需要的朋友可以參考一下2013-03-03
JS獲取鼠標(biāo)坐標(biāo)位置實(shí)例分析
這篇文章主要介紹了JS獲取鼠標(biāo)坐標(biāo)位置的方法,結(jié)合實(shí)例形式分析了JavaScript常見(jiàn)的獲取鼠標(biāo)頁(yè)面、屏幕等坐標(biāo)位置的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-01-01
php+js實(shí)現(xiàn)倒計(jì)時(shí)功能
由PHP傳入JS處理的時(shí)間戳我說(shuō)怎么老是對(duì)不上號(hào)呢,原來(lái)JS時(shí)間戳為13位,包含3位毫秒的,而PHP只有10位不包含毫秒的。恩,基礎(chǔ)還是要補(bǔ)補(bǔ)的2014-06-06
一步一步封裝自己的HtmlHelper組件BootstrapHelper(二)
一步一步封裝自己的HtmlHelper組件:BootstrapHelper,系列文章第二篇,感興趣的小伙伴們可以參考一下2016-09-09

