Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何优雅地链式取值 #51

Open
huruji opened this issue Sep 16, 2018 · 0 comments
Open

如何优雅地链式取值 #51

huruji opened this issue Sep 16, 2018 · 0 comments

Comments

@huruji
Copy link
Owner

huruji commented Sep 16, 2018

1523866942681

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

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]}
cosnt {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,方法三也不失为一种好的选择。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant