Sebastian Markbåge 提出的 Rest/Spread Properties 提案包括两部分:html
在对象解构模式下,rest 操做符会将解构源的除了已经在对象字面量中指明的属性以外的,全部可枚举自有属性拷贝到它的运算对象中。数组
const obj = {foo: 1, bar: 2, baz: 3};
const {foo, ...rest} = obj;
// Same as:
// const foo = 1;
// const rest = {bar: 2, baz: 3};
复制代码
若是你正在使用对象解构来处理命名参数,rest 操做符让你能够收集全部剩余参数:浏览器
function func({param1, param2, ...rest}) { // rest operator
console.log('All parameters: ',{param1, param2, ...rest}); // spread operator
return param1 + param2;
}
复制代码
在每一个对象字面量的顶层,能够使用 rest 操做符最多一次,而且必须只能在末尾出现:安全
const {...rest, foo} = obj; // SyntaxError
const {foo, ...rest1, ...rest2} = obj; // SyntaxError
复制代码
若是是嵌套结构,你能够屡次使用 rest 操做符:bash
const obj = {
foo: {
a: 1,
b: 2,
c: 3,
},
bar: 4,
baz: 5,
};
const {foo: {a, ...rest1}, ...rest2} = obj;
// Same as:
// const a = 1;
// const rest1 = {b: 2, c: 3};
// const rest2 = {bar: 4, baz: 5};
复制代码
对象字面量内部,spread 操做符将自身运算对象的全部可枚举的自有属性,插入到经过字面量建立的对象中:函数
> const obj = {foo: 1, bar: 2, baz: 3};
> {...obj, qux: 4}
{ foo: 1, bar: 2, baz: 3, qux: 4 }
复制代码
要注意的是顺序问题,即便属性 key 并不冲突,由于对象会记录插入顺序:ui
> {qux: 4, ...obj}
{ qux: 4, foo: 1, bar: 2, baz: 3 }
复制代码
若是 key 出现了冲突,后面的会覆盖前面的属性:es5
> const obj = {foo: 1, bar: 2, baz: 3};
> {...obj, foo: true}
{ foo: true, bar: 2, baz: 3 }
> {foo: true, ...obj}
{ foo: 1, bar: 2, baz: 3 }
复制代码
这一节,咱们会看看 spread 操做符的使用场景。我也会用 Object.assign() 实现一遍,它和 spread 操做符很类似(以后咱们会更详细地比较它们)。spa
拷贝对象 obj 的可枚举自有属性:prototype
const clone1 = {...obj};
const clone2 = Object.assign({}, obj);
复制代码
clone 对象们的原型都是 Object.prototype,它是全部经过对象字面量建立的对象的默认原型:
> Object.getPrototypeOf(clone1) === Object.prototype
true
> Object.getPrototypeOf(clone2) === Object.prototype
true
> Object.getPrototypeOf({}) === Object.prototype
true
复制代码
拷贝一个对象 obj,包括它的原型:
const clone1 = {__proto__: Object.getPrototypeOf(obj), ...obj};
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)), obj);
复制代码
注意,通常来讲,对象字面量内部的 proto 只是浏览器内置的特性,并不是 JavaScript 引擎全部。
有时候,你须要老老实实地拷贝对象的全部自有属性(properties)和特性(writable, enumerable, …),包括 getters 和 setters。这时候 Object.assign() 和 spread 操做符就回天乏术了。你须要使用属性描述符(property descriptors):
const clone1 = Object.defineProperties({},
Object.getOwnPropertyDescriptors(obj));
复制代码
若是还但愿保留 obj 的原型,能够用 Object.create()
:
const clone2 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
复制代码
“探索 ES2016 and ES2017” 里介绍了 Object.getOwnPropertyDescriptors()
咱们以前见过的全部拷贝对象的方式,都是浅拷贝:若是原始属性值是一个对象,拷贝的对象将指向同一个对象,它不会(递归的、深度的)拷贝自身:
const original = { prop: {} };
const clone = Object.assign({}, original);
console.log(original.prop === clone.prop); // true
original.prop.foo = 'abc';
console.log(clone.prop.foo); // abc
复制代码
合并 obj1 和 obj2 两个对象:
const merged = {...obj1, ...obj2};
const merged = Object.assign({}, obj1, obj2);
复制代码
给用户数据填充默认值
const DEFAULTS = {foo: 'a', bar: 'b'};
const userData = {foo: 1};
const data = {...DEFAULTS, ...userData};
const data = Object.assign({}, DEFAULTS, userData);
// {foo: 1, bar: 'b'}
复制代码
安全地更新属性 foo:
const obj = {foo: 'a', bar: 'b'};
const obj2 = {...obj, foo: 1};
const obj2 = Object.assign({}, obj, {foo: 1});
// {foo: 1, bar: 'b'}
复制代码
指定属性 foo 和 bar 的默认值:
const userData = {foo: 1};
const data = {foo: 'a', bar: 'b', ...userData};
const data = Object.assign({}, {foo:'a', bar:'b'}, userData);
// {foo: 1, bar: 'b'}
复制代码
spread 操做符和 Object.assign() 很类似。主要的区别在于前者定义了新属性,然后者还进行了赋值。稍后将解释这究竟意味着什么。
Object.assign() 有两种使用方式: 第一种,带有破坏性的(修改已有对象):
Object.assign(target, source1, source2);
复制代码
这里的 target 对象被修改了;source1 和 source2 被拷贝进去了。 第二种,非破坏性的(已有对象不会被修改):
const result = Object.assign({}, source1, source2);
复制代码
新对象是经过将 source1 和 source2 拷贝进一个空对象而生成的。最终,这个新对象被返回并赋值给 result。 spread 操做符相似于 Object.assign() 的第二种方式。接下来,咱们来看看二者的类似和不一样之处。
在写对象以前,二者都使用了 ”get“ 操做符去读取源对象的属性值。这一过程会将 getter 转换成正常的数据属性。 来看个例子:
const original = {
get foo() {
return 123;
}
};
复制代码
original 有一个 foo getter(它的属性描述符有 get 和 set 属性)
> Object.getOwnPropertyDescriptor(original, 'foo')
{ get: [Function: foo],
set: undefined,
enumerable: true,
configurable: true }
复制代码
可是在它拷贝的结果 clone1 和 clone2 里,foo 是一个正常的数据属性(属性描述符有value 和 writable 属性):
> const clone1 = {...original};
> Object.getOwnPropertyDescriptor(clone1, 'foo')
{ value: 123,
writable: true,
enumerable: true,
configurable: true }
> const clone2 = Object.assign({}, original);
> Object.getOwnPropertyDescriptor(clone2, 'foo')
{ value: 123,
writable: true,
enumerable: true,
configurable: true }
复制代码
spread 操做符在目标对象上定义了新的属性,而 Object.assign() 使用了一个 "set" 操做符来建立属性。这会致使两个结果:
首先,Object.assign() 触发 setter,而 spread 不会:
Object.defineProperty(Object.prototype, 'foo', {
set(value) {
console.log('SET', value);
},
});
const obj = {foo: 123};
复制代码
以上代码段设置了一个 foo setter,它会被全部普通对象继承。 若是咱们经过 Object.assign() 拷贝 obj,继承的 setter 会被触发:
> Object.assign({}, obj)
SET 123
{}
复制代码
而 spread 就不会:
> { ...obj }
{ foo: 123 }
复制代码
Object.assign() 在拷贝时还会触发自有 setter,这里并无发生重写。
第二,你能够经过继承只读属性,来阻止 Object.assign() 建立自有属性,但 spread 上这是作不到的:
Object.defineProperty(Object.prototype, 'bar', {
writable: false,
value: 'abc',
});
复制代码
以上代码设置了只读属性 bar,它会被全部普通对象继承。 这样,你就不再能使用赋值语句去建立自有属性 bar(严格模式下会抛一个异常,宽松模式会静默失败):
> const tmp = {};
> tmp.bar = 123;
TypeError: Cannot assign to read only property 'bar'
复制代码
下列代码,咱们使用对象字面量成功地建立了属性 bar。由于对象字面量没有设置属性,它只是定义了它们:
const obj = {bar: 123};
复制代码
然而,Object.assign() 使用赋值语句建立属性,这就是不能拷贝 obj 的缘由:
> Object.assign({}, obj)
TypeError: Cannot assign to read only property 'bar'
复制代码
经过 spread 操做符拷贝没有问题:
> { ...obj }
{ bar: 123 }
复制代码
它们都会忽略全部继承的属性和不可枚举的自有属性。 对象 obj 从 proto 继承了一个可枚举属性,而且有两个自有属性:
const proto = {
inheritedEnumerable: 1,
};
const obj = Object.create(proto, {
ownEnumerable: {
value: 2,
enumerable: true,
},
ownNonEnumerable: {
value: 3,
enumerable: false,
},
});
复制代码
若是拷贝 obj,结果将只有属性 ownEnumerable。属性 inheritedEnumerable 和 ownNonEnumerable 没有被拷贝:
> {...obj}
{ ownEnumerable: 2 }
> Object.assign({}, obj)
{ ownEnumerable: 2 }
复制代码
原文:http://exploringjs.com/es2018-es2019/ch_rest-spread-properties.html