In development, chain value is a very normal operation, such as:

res.data.goods.list[0].price
Copy the code

But for this type of operation, something like Uncaught TypeError is reported: Cannot read property ‘goods’ of undefined. If the res data is self-defined, it is more controllable, but if the data comes from different ends (such as the front and back ends), So this kind of data is not controllable for us, so in order to ensure the normal operation of the program, we need to check it:

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

If you were a little more subtle and checked everything, it would look something like this:

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

I can’t imagine what would happen if the data were a little bit more hierarchical, and this implementation would be really inelegant, but what if I could do it elegantly?

A, optional chaining

This is a new ecMA syntax for stage 2. Babel-plugin-transform-optional-chaining is a new syntax for stage 2. This syntax is available in 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
Copy the code

Parsing a string through a function

We can solve this problem by using functions to parse strings, an implementation of which is the _.get method of Lodash

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

It is also very simple to implement, just a simple string parsing:

function get (obj, props, def) {
    if((obj == null) || obj == null || typeofprops ! = ='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)
}
Copy the code
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
Copy the code

Use destruct assignment

Underscore this idea comes from a github repository called you-dont-need-lodash-underscore that I really appreciate

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
Copy the code

Of course, in order to avoid uncaught Typeerror at this point, we still need to define the default value, like this, which would seem unreadable without adding lint

const {a: {c: {d: result2} = {}}} = c
Copy the code

4. Use Proxy

This was mentioned by colleagues in the group, and a simple implementation is as follows:

function pointer(obj, path = []) {
    return new Proxy((a)= > {}, {
        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]}returnval; }})}Copy the code

We can use it like this:

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
Copy the code

That’s pretty much what elegance is all about.

In practice, method 4 is the most elegant and readable method, but depending on the browser, method 2 May be more common. Of course, if you’re not taking values too deep and your colleagues in your group need to be strict with Lint, method 3 is also a good choice.

The last is an advertisement, recently opened a new technology sharing public number, do not know how long it can last, welcome everyone to pay attention to 👇