美文网首页
ES6之扩展运算符与应用(对象篇)

ES6之扩展运算符与应用(对象篇)

作者: 雨飞飞雨 | 来源:发表于2017-09-26 15:37 被阅读2515次

    对象的扩展运算符

    《ES6之扩展运算符与应用(数组篇)》一文中,已经介绍过了扩展运算符(...).

    const [a,...b] = [1,2,3];
    a //1
    b //[2,3]
    

    ES2017将这个运算符引入了对象

    (1) 扩展运算符--解构赋值

    对象的解构赋值用于从一个对象取值,相当于将所有可遍历的,但尚未被读取的属性,分配到制定的对象上面,所有的键和它们的值,都会拷贝到新对象上面。

    let {x,y,...z} = {x:1,y:2,a:3,b:4};
    x // 1
    y // 2
    z // { a: 3, b: 4 }
    

    上面代码中,变量z是解构赋值所在的对象,它获取等号右边的所有尚未读取的键(a和b),将它们连同值一起拷贝过来。

    由于结构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象。

    let {x ,y ,...z} = null;//运行时报错
    let {x ,y ,...z} = undefined;//运行时报错
    

    解构赋值必须是最后一个参数,否则也会报错。

    let { ...x, y, z } = obj; // 句法错误
    let { x, ...y, ...z } = obj; // 句法错误
    

    上面代码中,解构赋值不是最后一个参数,所以会报错。

    注意,解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型的值(数组,对象,函数),那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

    let obj = {a:{b:1}}
    let {...x} = obj;
    obj.a.b = 2;
    x.a.b // 2;
    

    上面代码中,x解构赋值所在的对象,拷贝了对象obj的a属性,a属性引用了一个对象,修改这个对象的值,会影响到解构赋值对它的引用。
    另外,解构赋值不会拷贝继承自原型对象的属性。

    let o1 = {
        a:1
    };
    let o2 = {
        b:2
    };
    o2.__proto__ = o1;
    let {...o3 } = o2;
    o3//{b:2}
    o3.a //undefined
    

    上面代码中,对象o3复制了o2,但是只复制了o2自身的属性,没有复制它的原型对象o1的属性。

    下面是另一个例子。

    const o = Object.create({x:1,y:2});
    o.z = 3;
    
    let {x,...{y,z}} = o;
    x//1
    y//undefined
    z//3
    

    上面代码中,变量x是单纯的解构赋值,所以可以读取对象o继承的属性;变量y和z是双重解构赋值,只能读取对象自身的属性,所以只有变量z可以赋值成功。

    解构赋值的一个用处,是扩展某个函数的参数,引入其他操作。

    function baseFunction({a,b}){
        //...
    }
    function wrapperFunction({x,y,...restConfig}){
        //使用x和y参数进行操作
        //其余参数传给原始函数
        return baseFunction(restConfig);
    }
    

    上面代码中,原始函数baseFunction接受a和b作为参数,函数wrapperFunction在baseFunction的基础上进行了扩展,能够接受多余的参数,并且保留了原始函数的行为。

    (2) 扩展运算符--拷贝

    扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

    let z = {a:3,b:4};
    let n = {...z};
    n //{a:3,b:4}}
    

    这等同于使用Object.assign方法

    let aClone = {...a};
    //等同于
    let aClone = Object.assign({},a);
    

    上面的例子只是拷贝了对象实例的属性,如果想完整克隆一个对象,还拷贝对象原型的属性,可以采用下面的写法。

    //写法一
    const clone1 = {
        __proto__:Object.getPrototypeOf(obj),
        ...obj
    };
    //写法二
    const clone2 = {
        object.create(Object.getPrototypeOf(obj)),
        obj
    };
    

    上面代码中,写法一的proto属性在非浏览器的环境不一定部署,因此推荐使用写法二。

    (3) 扩展运算符--合并对象

    let ab = {...a,...b};
    //等同于
    //let ab = Object.assign({},a,b);
    

    如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。

    let aWithOverrides = {...a,x:1,y:2};
    //等同于
    let aWithOverride = {...a,...{x:1,y:2}};
    //等同于
    let x = 1,y =2,aWithOverrides = {...a,x,y};
    //等同于
    let aWithOverrides = Object.assign({},a,{x:1,y:2});
    

    上面代码中,a对象的x属性和y属性,拷贝到新对象后会被覆盖掉。

    这用来修改现有对象部分的属性就很方便了

    let newVersion = {
        ...previousVersion,
        name:'NewName'
    };
    

    上面代码中,newVersion对象自定义了name属性,其他属性全部复制自previousVersion对象。

    如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

    let aWithDefaults = {x:1,y:2,...a};
    //等同于
    let aWithDefaults = Object.assign({},{x:1,y:2},a);
    //等同于
    let aWithDefaults = Object.assign({x:1,y:2},a);
    

    与数组的扩展运算符一样,对象的扩展运算符后面可以更表达式。

    const obj = {
        ...{x > 1 ? {a:1}:{}},
        b:2
    };
    

    如果扩展运算符后面是一个空对象,则没有任何效果。

    {...{},a:1}
    //{a:1}
    

    如果扩展运算符的参数是null或是undefined,这两个值会被忽略,不会报错。

    let emptyObject = (...null,...undefined);//不报错
    

    扩展运算符的参数对象之中,如果有取值函数get,这个函数是会执行的。

    // 并不会抛出错误,因为 x 属性只是被定义,但没执行
    let aWithXGetter = {
      ...a,
      get x() {
        throw new Error('not throw yet');
      }
    };
    
    // 会抛出错误,因为 x 属性被执行了
    let runtimeError = {
      ...a,
      ...{
        get x() {
          throw new Error('throw now');
        }
      }
    };
    

    学习文章:ES6之对象的扩展 第十章

    相关文章

      网友评论

          本文标题:ES6之扩展运算符与应用(对象篇)

          本文链接:https://www.haomeiwen.com/subject/erwsextx.html