假设我有一个选项变量我想设置一个默认值。

这两种选择的优点/缺点是什么?

使用对象扩展

options = {...optionsDefault, ...options};

或者使用Object.assign

options = Object.assign({}, optionsDefault, options);

这是让我疑惑的承诺。


当前回答

两者之间有着巨大的差异,并会带来非常严重的后果。投票最多的问题甚至没有触及到这一点,关于物体传播是一项提案的信息在2022年已经无关紧要了。

区别在于对象。赋值操作将对象就地更改,而展开操作符(…)将创建一个全新的对象,这将破坏对象引用相等性。

首先,让我们看看效果,然后我将给出一个现实世界的例子,说明理解这种根本差异是多么重要。

首先,让我们使用Object.assign:

// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using Object.assign, adding a new `foo` key with `bar` value;
Object.assign(parentObject.childObject, { foo: 'bar' });

// childObject is still the same object in memory, it was changed IN PLACE.
parentObject.childObject === childObject
// true

现在对展开运算符进行同样的练习:

// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using the spread operator;
parentObject.childObject = {
  ...parentObject.childObject,
  foo: 'bar',
}

// They are not the same object in memory anymore!
parentObject.childObject === childObject;
// false

很容易看出发生了什么,因为在parentObject上。childObject ={…}我们清楚地将parentObject中的childObject键的值赋给一个全新的对象文字,而它是由旧的childObject内容组成的事实是无关紧要的。这是一个新物件。

如果你认为这在实践中是无关紧要的,让我展示一个真实世界的场景来理解它有多重要。

在一个非常大的Vue.js应用程序中,当我们在输入字段中输入客户的名字时,我们开始注意到很多延迟。

经过大量调试后,我们发现输入中的每个字符都会触发一大堆需要重新计算的计算属性。

这是意料之外的,因为在那些计算函数中根本没有使用客户的名称。只使用了其他客户数据(如年龄、性别)。发生了什么事?当客户的名字改变时,vue为什么要重新计算所有这些计算函数?

我们有一家Vuex商店是这样做的:

mutations: {
  setCustomer(state, payload) {
    // payload being { name: 'Bob' }
    state.customer = { ...state.customer, ...payload };
  }

我们的计算是这样的:

veryExpensiveComputed() {
   const customerAge = this.$store.state.customer.age;
}

所以,瞧!当客户名称更改时,Vuex突变实际上是将其完全更改为一个新对象;由于计算依赖于该对象来获取客户年龄,Vue依赖于该非常特定的对象实例作为依赖项,当它被更改为一个新对象时(未能通过===对象相等性测试),Vue决定是时候重新运行计算函数了。

这是固定的吗?使用对象。赋值不丢弃前一个对象,而是在适当的位置更改它…

mutations: {
  setCustomer(state, payload) {
    // payload being same as above: { name: 'Bob' }
    Object.assign(state.customer, payload);
  }

顺便说一句,如果你在ve2中,你不应该使用Object。因为Vue 2不能直接跟踪这些对象的变化,但同样的逻辑适用,只是使用Vue。set而不是Object.assign:

mutations: {
  setCustomer(state, payload) {
    Object.keys(payload).forEach(key => {
      Vue.set(state.customer, key, payload[key])
    })
  }

其他回答

两者之间有着巨大的差异,并会带来非常严重的后果。投票最多的问题甚至没有触及到这一点,关于物体传播是一项提案的信息在2022年已经无关紧要了。

区别在于对象。赋值操作将对象就地更改,而展开操作符(…)将创建一个全新的对象,这将破坏对象引用相等性。

首先,让我们看看效果,然后我将给出一个现实世界的例子,说明理解这种根本差异是多么重要。

首先,让我们使用Object.assign:

// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using Object.assign, adding a new `foo` key with `bar` value;
Object.assign(parentObject.childObject, { foo: 'bar' });

// childObject is still the same object in memory, it was changed IN PLACE.
parentObject.childObject === childObject
// true

现在对展开运算符进行同样的练习:

// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using the spread operator;
parentObject.childObject = {
  ...parentObject.childObject,
  foo: 'bar',
}

// They are not the same object in memory anymore!
parentObject.childObject === childObject;
// false

很容易看出发生了什么,因为在parentObject上。childObject ={…}我们清楚地将parentObject中的childObject键的值赋给一个全新的对象文字,而它是由旧的childObject内容组成的事实是无关紧要的。这是一个新物件。

如果你认为这在实践中是无关紧要的,让我展示一个真实世界的场景来理解它有多重要。

在一个非常大的Vue.js应用程序中,当我们在输入字段中输入客户的名字时,我们开始注意到很多延迟。

经过大量调试后,我们发现输入中的每个字符都会触发一大堆需要重新计算的计算属性。

这是意料之外的,因为在那些计算函数中根本没有使用客户的名称。只使用了其他客户数据(如年龄、性别)。发生了什么事?当客户的名字改变时,vue为什么要重新计算所有这些计算函数?

我们有一家Vuex商店是这样做的:

mutations: {
  setCustomer(state, payload) {
    // payload being { name: 'Bob' }
    state.customer = { ...state.customer, ...payload };
  }

我们的计算是这样的:

veryExpensiveComputed() {
   const customerAge = this.$store.state.customer.age;
}

所以,瞧!当客户名称更改时,Vuex突变实际上是将其完全更改为一个新对象;由于计算依赖于该对象来获取客户年龄,Vue依赖于该非常特定的对象实例作为依赖项,当它被更改为一个新对象时(未能通过===对象相等性测试),Vue决定是时候重新运行计算函数了。

这是固定的吗?使用对象。赋值不丢弃前一个对象,而是在适当的位置更改它…

mutations: {
  setCustomer(state, payload) {
    // payload being same as above: { name: 'Bob' }
    Object.assign(state.customer, payload);
  }

顺便说一句,如果你在ve2中,你不应该使用Object。因为Vue 2不能直接跟踪这些对象的变化,但同样的逻辑适用,只是使用Vue。set而不是Object.assign:

mutations: {
  setCustomer(state, payload) {
    Object.keys(payload).forEach(key => {
      Vue.set(state.customer, key, payload[key])
    })
  }

太多的错误答案……

我搜索了一下,发现了很多关于这方面的错误信息。

总结

既不……扩散也不反对。赋值更快。视情况而定。 对象。如果不考虑副作用/对象突变,Assign几乎总是更快。 除了性能之外,使用这两种方法通常都没有优点或缺点,直到遇到极限情况,例如复制包含getter /setter的对象或只读属性。点击这里阅读更多。

性能

是否对象。Assign或…传播速度更快,这取决于你想要组合的东西,以及你正在使用的运行时(实现和优化不时发生)。对于小的对象,这无关紧要。

对于较大的对象,请使用Object。Assign通常更好。特别是在不需要关心副作用的情况下,只需向第一个对象添加属性就可以节省时间,而不是复制两个对象并创建一个全新的对象。看到的:

async function retrieveAndCombine() {
    const bigPayload = await retrieveData()
    const smallPayload = await retrieveData2()
    
    // only the properties of smallPayload is iterated through
    // whereas bigPayload is mutated.
    return Object.assign(bigPayload, smallPayload)
}

如果有副作用的话

在副作用很重要的情况下,例如将一个对象与另一个对象组合作为参数传入。

在下面的例子中,bigPayload将被改变,如果retrieveAndCombine之外的其他函数/对象依赖于bigPayload,这是一个坏主意。在这种情况下,可以交换传递给Object的参数。赋值,或者直接使用{}作为第一个参数来创建一个新对象:

async function retrieveAndCombine(bigPayload) {
    const smallPayload = await retrieveData2()

    // this is slightly more efficient
    return Object.assign(smallPayload, bigPayload)

    // this is still okay assuming `smallPayload` only has a few properties
    return Object.assign({}, smallPayload, bigPayload)
}

Test

试试下面的代码片段。注意:运行需要一段时间。

const rand = () => (Math.random() + 1).toString(36).substring(7) function combineBigObjects() { console.log('Please wait...creating the test objects...') const obj = {} const obj2 = {} for (let i = 0; i < 100000; i++) { const key = rand() obj[rand()] = { [rand()]: rand(), [rand()]: rand(), } obj2[rand()] = { [rand()]: rand(), [rand()]: rand(), } } console.log('Combine big objects using spread:') console.time() const result1 = { ...obj, ...obj2, } console.timeEnd() console.log('Combine big objects using assign:') console.time() Object.assign({}, obj, obj2) console.timeEnd() console.log('Combine big objects using assign, but mutates first obj:') console.time() Object.assign(obj, obj2) console.timeEnd() } combineBigObjects() function combineSmallObjects() { const firstObject = () => ({ [rand()]: rand() }) const secondObject = () => ({ [rand()]: rand() }) console.log('Combine small objects using spread:') console.time() for (let i = 0; i < 500000; i++) { const finalObject = { ...firstObject(), ...secondObject(), } } console.timeEnd() console.log('Combine small objects using assign:') console.time() for (let i = 0; i < 500000; i++) { const finalObject = Object.assign({}, firstObject(), secondObject()) } console.timeEnd() console.log('Combine small objects using assign, but mutates first obj:') console.time() for (let i = 0; i < 500000; i++) { const finalObject = Object.assign(firstObject(), secondObject()) } console.timeEnd() } combineSmallObjects()

我想总结一下“扩展对象合并”ES特性在浏览器和工具生态系统中的状态。

Spec

https://github.com/tc39/proposal-object-rest-spread 这个语言特性是第4阶段的提案,这意味着它已经被合并到ES语言规范中,但还没有被广泛实现。

浏览器:Chrome, SF, Firefox(60版本,IIUC)

浏览器支持Chrome 60中附带的“传播属性”,包括这个场景。 在当前的Firefox(59)中不支持此场景,但在我的Firefox开发者版中可以。所以我相信它会在Firefox 60中发布。 Safari:未测试,但Kangax说它可以在桌面Safari 11.1中运行,但不适用于SF 11 iOS Safari:未测试,但Kangax说它可以在iOS 11.3运行,但不能在iOS 11运行 还没有在Edge中

工具:Node 8.7, TS 2.1

NodeJS从8.7开始支持(通过Kangax)。9.8在本地测试时确认。 TypeScript从2.1开始支持它,目前是2.8

链接

Kangax“财产蔓延” https://davidwalsh.name/merge-objects

代码示例(兼作兼容性测试)

var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }

再次强调:在编写此示例时,无需编译即可在Chrome(60+)、Firefox Developer Edition (Firefox 60预览版)和Node(8.7+)中工作。

为什么答案?

我是在最初的问题提出两年半后写这篇文章的。但我也有同样的问题,谷歌让我来这里。我是SO改善长尾理论使命的奴隶。

Since this is an expansion of "array spread" syntax I found it very hard to google, and difficult to find in compatibility tables. The closest I could find is Kangax "property spread", but that test doesn't have two spreads in the same expression (not a merge). Also, the name in the proposals/drafts/browser status pages all use "property spread", but it looks to me like that was a "first principal" the community arrived at after the proposals to use spread syntax for "object merge". (Which might explain why it is so hard to google.) So I document my finding here so others can view, update, and compile links about this specific feature. I hope it catches on. Please help spread the news of it landing in the spec and in browsers.

最后,我本可以将这些信息作为评论添加进来,但我无法在不破坏作者原始意图的情况下编辑它们。具体来说,我无法编辑@ chillpenguin的评论,否则它就会失去纠正@RichardSchulte的意图。但多年后,理查德(在我看来)证明是对的。所以我写了这个答案,希望它最终能取代旧的答案(可能需要几年时间,但毕竟这就是长尾效应的意义所在)。

展开运算符将数组展开到函数的单独参数中。

let iterableObjB = [1,2,3,4]
function (...iterableObjB)  //turned into
function (1,2,3,4)

正如其他人所提到的,在撰写本文时,object .assign()需要一个polyfill和object spread…需要一些蒸煮(也许是一个填充)为了工作。

考虑下面的代码:

// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);

它们都产生相同的输出。

下面是从Babel到ES5的输出:

var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);

这是我目前的理解。object .assign()实际上是标准化的,其中随着对象扩展…还没有。唯一的问题是浏览器对前者以及未来对后者的支持。

玩一下这里的代码

希望这能有所帮助。