2022年 12月
 1234
567891011
12131415161718
19202122232425
262728293031  

近期发布

近期评论

    2022 年 12 月 3 日

    Neusofts

    科技改变生活,创新引领未来

    ES6/ES2015解构及扩展运算符

    Javascript ES6/ES2015,其中许多特性其实是为了简化代码。解构运算符扩展运算符rest运算符就是其中很好的特性,它们可以通过减少赋值语句的使用,或者减少通过下标访问数组或对象的方式,使代码更加简洁优雅,可读性更佳。

     

    解构

    解构的作用是可以快速取得数组或对象当中的元素或属性,而无需使用arr[index]或者obj[key]等传统方式进行赋值

    数组解构赋值:

    let arr = ['字符串', 11, true];
    
    // 传统方式:
    var a = arr[0], b = arr[1], c = arr[2];
    
    // 解构赋值,效果相同
    let [a, b, c] = arr;
    

     

    嵌套数组解构:

    let arr = [['字符串', 11, true], [1, 2, 3], [4, 5, 6]];
    
    let [[a, b, c], d, e] = arr;
    // 结果:a == '字符串',b == 11,c == true,d == [1, 2, 3], e == [4, 5, 6]

     

    函数传参解构:

    let arr = ['字符串', 11, true];
    
    ;(function ([a, b, c]) {
        // 结果:a == '字符串',b == 11,c == true
    })(arr);

     

    for循环解构:

    let arr = [ ['字符串', 11, true], [1, 2, 3], [4, 5, 6] ];
    
    for (let [a, b, c] of arr) {
        // 字符串 11 true
        // 1 2 3
        // 4 5 6
    }

     

    对象赋值解构:

    let obj = {
        name: '名字',
        age: 22,
        eat: {
            food1: '苹果',
            food2: '香蕉'
        }
    };
    
    let {name, age, eat} = obj;
    // 结果:name == '名字',age == 22, eat == [object Object]
    
    let {food1, food2} = eat;
    // 结果:food1 == '苹果',food2 == '香蕉'

     

    对象传参解构:

    let obj = {
        name: '名字',
        age: 22,
        eat: {
            food1: '苹果',
            food2: '香蕉'
        }
    };
    
    ;(function ({name, age, eat}) {
        // 结果:name == '名字',age == 22, eat == [object Object]
    })(obj);
    

     

    变量名与对象属性名不一致解构(指定别名):

    let obj = {
        name: '名字',
        age: 22
    };
    
    let {name: n, age: a} = obj;
    // 结果:n == '名字',a == 22
    

     

    对象嵌套解构:

    let obj = {
        name: '名字',
        age: 22,
        eat: {
            food1: '苹果',
            food2: '香蕉'
        }
    };
    
    let { name, age, eat: {food1, food2} } = obj;
    // 结果:name == '名字',age == 22, food1 = '苹果',food2 == '香蕉'(附注:age不存在)
    

     

    对象嵌套重名解构:

    let obj = {
        name: '名字1',
        age: 22,
        friend: {
            name: '名字2',
            age: 20
        }
    };
    
    let { name, age, friend: {name: n, age: a} } = obj;
    // 结果:name == '名字1',age == 22, n == '名字2',a == 20(附注:friend不存在)
    

     

    循环解构对象:

    let arr = [ {name: 'zs', age: 20}, {name: 'ls', age: 21}, {name: 'ww', age: 22} ];
    for (let {name, age} of arr) {
        // zs 20
        // ls 21
        // ww 22
    }
    

     

    解构特殊场景:

    // 字符串处理
    let str = 'abc';
    let [a, b, c, d] = str;
    // 结果:a == 'a', b == 'b', c == 'c', d == 'undefined'
    
    // 值互换
    let x = 1, y = 2;
    [x, y] = [y, x]; // 不能let定义,因为已存在
    // 结果 x == 2, y == 1;
    
    //对任意深度的嵌套数组进行解构
    var [foo, [ [bar], baz] ] = [1, [ [2], 3] ];
    console.log(foo); // 1
    console.log(bar); // 2
    console.log(baz); // 3
    
    // 忽略尾随元素
    let [first] = [1, 2, 3, 4];
    console.log(first); // 1
    
    //忽略部分元素
    let [, second, , forth] = [1, 2, 3, 4];
    console.log(second); // 2
    console.log(forth); // 4
    
    // 没有声明的赋值
    ({a, b} = {a:'foo', b:12, c:'bar'}); // 注意此处,需要用括号括起来,因为javascript通常会以{起始的语句作为一个代码块。
    
    // 对象展开(属性值覆盖)
    let default = {food:'spicy', price:'$', drink:'coko'};
    let search = {...default, food:'rich'};
    console.log(search); // {food:'rich', price:'$', drink:'coko'}
    
    // 声明给出默认值
    let obj = {a: 1, b: 2, c: 3};
    let {a, b, c, d = 4} = obj; // a,b,c,d分别等于1,2,3,4
    
    // 对象展开(附注:typescript语法)它只包含自身的可枚举的属性。 并且会丢失展开对象的方法:
    class C = {p:12, m(){}};
    let c = new C();
    let clone = {...c};
    console.log(clone.p); // 12
    console.log(clone.m); // error!
    
    // 指定类型(附注:typescript语法)
    let {a, b} : {a:string, b:number} = o;
    
    // 指定默认值(附注:typescript语法) (即使b为undefined,obj的属性a, b也都会有值)
    function keepWhole(obj: { a: string, b?: number }) {
        let { a, b = 1001 } = obj;
    };
    

     

    拓展运算符

    扩展运算符用三个点号 ... 表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值

     

    var arr = [1, 2, 3];
    var fn = function (a, b, c) {
        // 结果:a == 1, b == 2, c == 3;
    }
    
    // 传统方式:
    fn(arr[0], arr[1], arr[2]);
    
    // 拓展运算符方式
    fn(...arr);

     

    特殊应用场景:

    var arr = [1, 2, 3];
    
    // 数组深度拷贝
    var arr2 = arr; // arr2 === arr,指向同一引用
    var arr3 = [...arr]; // arr3 !== arr,非指向同一引用
    
    // 数组插入
    var arr4 = [...arr, 4, 5, 6]; // 结果:[1, 2, 3, 4, 5, 6]
    
    // 字符串转数组
    var str = 'abcd';
    var arr5 = [...str]; // 结果:['a', 'b', 'c', 'd']

     

    rest运算符

    rest运算符也是三个点号,不过其功能与扩展运算符恰好相反,把逗号隔开的值序列组合成一个数组

    //主要用于不定参数,所以ES6开始可以不再使用arguments对象

    ;(function (...args) {
        for (let el of args) {
            // 输出:1 2 3 4
        }
    })(1, 2, 3, 4);
    
    ;(function (a, ...args) {
        // a === 1
        // args == [2, 3, 4]
    })(1, 2, 3, 4);
    

     

    rest运算符配合解构使用:

    let [a, ...args] = [1, 2, 3, 4];
    // a === 1
    // args == [2, 3, 4]

     

    小结:

    =表达式是典型的赋值形式,函数传参和for循环的变量都是特殊形式的赋值。解构的原理是赋值的两边具有相同的结构,就可以正确取出数组或对象里面的元素或属性值,省略了使用下标逐个赋值的麻烦。

    对于三个点号...,放在形参或者等号左边rest运算符; 放在实参或者等号右边spread运算符(扩展运算符),或者说,放在被赋值一方为rest运算符,放在赋值一方为扩展运算符。

    总结:

    1、在等号赋值或for循环中,如果需要从数组或对象中取值,尽量使用解构。
    2、在自定义函数时,如果调用者传来的是数组或对象,形参(定义)尽量使用解构方式,优先使用对象解构,其次是数组解构。代码可读性会很好。
    3、在调用第三方函数的时候,如果该函数接受多个参数,并且你要传入的实参(调用)为数组,则使用扩展运算符。可以避免使用下标形式传入参数。也可以避免很多人习惯的使用apply方法传入数组。
    4、rest运算符使用场景应该稍少一些,主要是处理不定数量参数,可以避免arguments对象的使用。