如何优雅地链式取值

2018 年 11 月 1 日 前端大全

(给前端大全加星标,提升前端技能


作者:@灰风GreyWind

https://juejin.im/post/5ba08483e51d450e99430a7f


开发中,链式取值是非常正常的操作,如:

res.data.goods.list[0].price

但是对于这种操作报出类似于Uncaught TypeError: Cannot read property 'goods' of undefined 这种错误也是再正常不过了,如果说是res数据是自己定义,那么可控性会大一些,但是如果这些数据来自于不同端(如前后端),那么这种数据对于我们来说我们都是不可控的,因此为了保证程序能够正常运行下去,我们需要对此校验:

if (res.data.goods.list[0] && res.data.goods.list[0].price) {
// your code
}

如果再精细一点,对于所有都进行校验的话,就会像这样:

if (res && res.data && res.data.goods && res.data.goods.list && res.data.goods.list[0] && res.data.goods.list[0].price){
// your code
}

不敢想象,如果数据的层级再深一点会怎样,这种实现实在是非常不优雅,那么如果优雅地来实现链式取值呢?

一、optional chaining

这是一个出于stage 2的ecma新语法,目前已经有了babel的插件 babel-plugin-transform-optional-chaining,这种语法在swift中有,可以看下官方给的实例

a?.b                          // undefined if `a` is null/undefined, `a.b` otherwise.
a
== null ? undefined : a.b
a
?.[x]                        // undefined if `a` is null/undefined, `a[x]` otherwise.
a
== null ? undefined : a[x]
a
?.b()                        // undefined if `a` is null/undefined
a
== null ? undefined : a.b() // throws a TypeError if `a.b` is not a function// otherwise, evaluates to `a.b()`
a
?.()                        // undefined if `a` is null/undefined
a
== null ? undefined : a()  // throws a TypeError if `a` is neither null/undefined, nor a function// invokes the function `a` otherwise

二、通过函数解析字符串

我们可以通过函数解析字符串来解决这个问题,这种实现就是lodash的 _.get 方法

var object = { a: [{ b: { c: 3 } }] };
var result = _.get(object, 'a[0].b.c', 1);
console
.log(result);
// output: 3

实现起来也非常简单,只是简单的字符串解析而已:

function get (obj, props, def) {
   
if((obj == null) || obj == null || typeof props !== 'string') return def;
   
const temp = props.split('.');
   
const fieldArr = [].concat(temp);
   temp
.forEach((e, i) => {
       
if(/^(\w+)\[(\w+)\]$/.test(e)) {
           
const matchs = e.match(/^(\w+)\[(\w+)\]$/);
           
const field1 = matchs[1];
           
const field2 = matchs[2];
           
const index = fieldArr.indexOf(e);
           fieldArr
.splice(index, 1, field1, field2);
       
}
   
})
   
return fieldArr.reduce((pre, cur) => {
       
const target = pre[cur] || def;
       
if(target instanceof Array) {
           
return [].concat(target);
       
}
       
if(target instanceof Object) {
           
return Object.assign({}, target)
       
}
       
return target;
   
}, obj)
}
var c = {a: {b : [1,2,3] }}
get(c ,'a.b')     // [1,2,3]
get(c, 'a.b[1]')  // 2
get(c, 'a.d', 12)  // 12

三、使用解构赋值

这个思路是来自github上 You-Dont-Need-Lodash-Underscore 这个仓库,看到这个的时候真的佩服

const c = {a:{b: [1,2,3,4]}}
const { a: result } = c;
// result : {b: [1,2,3,4]}const {a: { c: result = 12 }} = c
// result: 12

当然,这个时候为了保证不报uncaught Typeerror,我们仍然需要定义默认值, 就像这样, 貌似如果不加lint可读性堪忧

const {a: {c: {d: result2} = {}}} = c

四、使用Proxy

这个是组内同事提到的,一个简单实现如下:

function pointer(obj, path = []) {
   
return new Proxy({}, {
       
get (target, property) {
           
return pointer(obj, path.concat(property))
       
},
       apply
(target, self, args) {
           
let val = obj;
           
let parent;
           
for(let i = 0; i < path.length; i++) {
               
if(val === null || val === undefined) break;
               parent
= val;
               val
= val[path[i]]    
           
}
           
if(val === null || val === undefined) {
               val
= args[0]
           
}
           
return val;
       
}
   
})
}

我们可以这样使用:

let c = {a: {b: [1, ,2 ,3]}}
pointer
(c).a();   // {b: [1,2,3]}
pointer
(c).a.b(); // [1,2,3]
pointer
(d).a.b.d('default value');  // default value复制代码

这差不多就是心中所谓的优雅了。

综上,在实际工作中,使用方法四会是最优雅,可读性也非常强,但考虑到浏览器的话,可能方法二会更加常用,当然,如果你所要取的值层级不是太深,你组内的同事要严格的lint,方法三也不失为一种好的选择。


推荐阅读

(点击标题可跳转阅读)

你点的 ES6 小技巧,请查收

10 分钟理解 JS 引擎的执行机制

深入理解 ES Modules (手绘示例)



觉得本文对你有帮助?请分享给更多人

关注「前端大全」加星标,提升前端技能


登录查看更多
0

相关内容

还在修改博士论文?这份《博士论文写作技巧》为你指南
简明扼要!Python教程手册,206页pdf
专知会员服务
46+阅读 · 2020年3月24日
《动手学深度学习》(Dive into Deep Learning)PyTorch实现
专知会员服务
116+阅读 · 2019年12月31日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
94+阅读 · 2019年12月4日
机器学习入门的经验与建议
专知会员服务
90+阅读 · 2019年10月10日
如何给你PyTorch里的Dataloader打鸡血
极市平台
15+阅读 · 2019年5月21日
你头疼的ELK难题,本文几乎都解决了
DBAplus社群
8+阅读 · 2019年3月20日
怎么画高大上的神经网络结构?试试这个!
Python3.8新特性概览
Python程序员
4+阅读 · 2018年12月8日
如何使用高大上的方法调参数
AI研习社
3+阅读 · 2017年9月11日
python pandas 数据处理
Python技术博文
3+阅读 · 2017年8月30日
【推荐】(Keras)LSTM多元时序预测教程
机器学习研究会
24+阅读 · 2017年8月14日
代码这样写不止于优雅(Python版)
数说工作室
4+阅读 · 2017年7月17日
Parsimonious Bayesian deep networks
Arxiv
5+阅读 · 2018年10月17日
Arxiv
5+阅读 · 2018年5月1日
VIP会员
相关资讯
如何给你PyTorch里的Dataloader打鸡血
极市平台
15+阅读 · 2019年5月21日
你头疼的ELK难题,本文几乎都解决了
DBAplus社群
8+阅读 · 2019年3月20日
怎么画高大上的神经网络结构?试试这个!
Python3.8新特性概览
Python程序员
4+阅读 · 2018年12月8日
如何使用高大上的方法调参数
AI研习社
3+阅读 · 2017年9月11日
python pandas 数据处理
Python技术博文
3+阅读 · 2017年8月30日
【推荐】(Keras)LSTM多元时序预测教程
机器学习研究会
24+阅读 · 2017年8月14日
代码这样写不止于优雅(Python版)
数说工作室
4+阅读 · 2017年7月17日
Top
微信扫码咨询专知VIP会员