引子
获取一个深层次的对象,对大家来说都是家常便饭的事情了。像自己封装的、三方库的以及 es2020提供的,总之和大家分享探讨一下
取用户的所在的城市地址
const user = {
name: 'xxx',
address: {
city: 'hangzhou'
}
};
最直接的
const ctiy = user && user.address && user.address.city;
// or
const city = !user ? undefined : !user.address ? undefined : user.address.city;
当对象每增加一行的时候,判断就得多需要一层 在我明确知道路径的情况下 user.name.address.city, 能不能自己写个解析的函数去获取值呢
手动实现版
var user = {
name: 'xxx',
address: {
city: 'hangzhou'
}
};
const str = 'address.city';
const get = (obj, path, defaultValue) => {
let result = obj, i = 0;
const paths = path.split('.');
while (i < paths.length) {
result = Object(result)[paths[i]];
if (result === undefined) {
return defaultValue;
}
i++
}
return result;
};
console.log(get(user, str), '--');
// hangzhou
我们这里其实每考虑数组下标的情况,要加上也简单只需要加上一行就行
优化版
var user = {
name: 'xxx',
address: {
cities: [{city: 'hangzhou'}]
}
};
const str = 'address.cities[0].city';
const get = (obj, path, defaultValue) => {
let result = obj, i = 0;
//新增这一行————————————————————————————
const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.')
console.log(paths, '--')
while (i < paths.length) {
result = Object(result)[paths[i]];
if (result === undefined) {
return defaultValue;
}
i++;
}
return result;
};
console.log(get(user, str), '--');
// 杭州
大佬一行版
var mb=p=>o=>p.map(c=>o=(o||{})[c])&&o
有个大佬写了一个 mb.js,其实就一行通过数组的方式来实现 get
var getHello = mb(["a", "b", 0, "hello"]);
var getHelloLength = mb(["a", "b", 0, "hello", "length"]);
var obj1 = {
a: {
b: [{ hello: "world" }]
}
};
var obj2 = {
c: {
d: "e"
}
};
getHello(obj1); // world
getHelloLength(obj1); // 5
getHello(obj2); // undefined
getHelloLength(obj2); // undefined
上面这么多骚操作,只要你定义好自己的解析器都可以实现
lodash/get
const city = get(user, 'address.cities[0].city'))
让我们瞅瞅源码 ./get.js
import baseGet from './.internal/baseGet.js'
function get(object, path, defaultValue) {
// 过程比较简单,对undefined值判断取默认值defaultValue,所有取值逻辑都在`baseGet`模块内。
const result = object == null ? undefined : baseGet(object, path)
return result === undefined ? defaultValue : result
}
export default get
看看 baseGet.js
import castPath from './castPath.js'
import toKey from './toKey.js'
function baseGet(object, path) {
// 所有路径转为数组形式
path = castPath(path, object)
let index = 0
const length = path.length
while (object != null && index < length) {
// 听名字就知道是转为 唯一的 key
object = object[toKey(path[index++])]
}
// 判断一下 key 数组
return (index && index == length) ? object : undefined
}
export default baseGet
/*------------------------------*/
//分割线 castPath.js
import isKey from './isKey.js'
import stringToPath from './stringToPath.js'
function castPath(value, object) {
if (Array.isArray(value)) {
return value
}
// 转为key 数组
return isKey(value, object) ? [value] : stringToPath(value)
}
export default castPath
// isKey.js
function isKey(value, object) {
if (isArray(value)) { // 数组,直接返回false
return false;
}
if (type == 'number' || type == 'symbol' || type == 'boolean' ||
value == null || isSymbol(value)) {
return true;
}
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
(object != null && value in Object(object));
}
看完以后是不是感觉 lodash 边界情况下考虑的特别好,比如这里 isKey(value, object) 用来判断value是object的key, 像我自己写的直接 object[value] 来判断了
Ramda
lodash 的 get 是一步到位,Ramda 就是打组合拳的形式,毕竟 ramda 是一个函数式编程库,让我们来看看实现
// 一步到位版本,这个没有默认值选项
const city = R.path(['address', 'cities', 0, 'city'])(user)
// 带默认参数版
R.pathOr([], ['user', 'posts', 0, 'comments'])
//组合
const city = R.compose(
R.prop('city'),
R.path(['address, cities', 0,])(user)
类的方式
const user = {
address: {
city: 'hangzhou'
}
}
class GetValue {
constructor(val) {
this.__val = val;
}
static set(val) {
return new GetValue(val);
}
isNothing() {
return this.__val === null || this.__val === undefined;
}
map(f) {
return this.isNothing() ? new GetValue(null) : new GetValue(f(this.__val));
}
}
const prop = key => obj => obj[key]
const city = GetValue.set(user).map(prop('address')).map(prop('city'))
console.log(city, 'city')
// GetValue { __val: 'hangzhou' } city
我们上面折腾了这么久,其实在 es2020 早就提供了一种新的方法帮我们做了
可选链操作符(Optional Chaining)
可选链 我们在查询多层对象时,不需要在各种判空
const city = address?.cities[0]?.city
当左边为空值时,就取右边的值,否则取左边的值。这里的空值指的是 null 和 undefined ,这是它跟假值不同的地方,也是使用空值运算符和逻辑或运算符的区别之处
兼容性

前置条件
- nodejs v>= 14.0.0
- TypeScript v>=3.7
- babel 插件支持
// install
yarn add @babel/plugin-proposal-optional-chaining
--dev //可选链
yarn add @babel/plugin-proposal-nullish-coalescing-operato --save //双问号
// use
{
"plugins": ["@babel/,plugin-proposal-optional-chaining"],
"@babel/plugin-proposal-nullish-coalescing-operator"
}
- 框架主要不支持 vue template
更多🌰
动态 key
const obj = { a: { b: 'b' } }
const name = 'b'
const b = obj?.a?.[name]
三元
const city = user ? user.name : 'xxx' // 之前
const city = user ?.name ?? 'xxx' //之后
更多的用法大家自行挖掘~~
用了以后我的代码就变成了各种问号

最后
虽然我们折腾了这么久,估计也就写爬虫的场景用的到比较深对象,要是工作中你遇到后端给你返回这么深的数据结构,该喷就喷,别整这些没用的