对象的扩展运算符
《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之对象的扩展 第十章
网友评论