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

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

使用对象扩展

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

或者使用Object.assign

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

这是让我疑惑的承诺。


当前回答

作为参考,对象rest/spread在ECMAScript 2018中作为第4阶段完成。提案可以在这里找到。

在大多数情况下,对象赋值和扩展的工作方式是相同的,关键的区别是扩展定义属性,而object .assign()设置属性。这意味着Object.assign()触发设置器。

值得记住的是,除此之外,对象rest/spread 1:1映射到object .assign(),与数组(可迭代)spread的作用不同。例如,当展开一个数组时,将展开空值。然而,使用对象传播时,空值会被无声地传播到空值。

数组(可迭代)扩展示例

const x = [1, 2, null , 3];
const y = [...x, 4, 5];
const z = null;

console.log(y); // [1, 2, null, 3, 4, 5];
console.log([...z]); // TypeError

对象扩展示例

const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};

console.log(z); //{a: 1, b: 2}

这与Object.assign()的工作方式是一致的,两者都无声地排除空值而没有错误。

const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);

console.log(z); //{a: 1, b: 2}

其他回答

正如其他人所提到的,在撰写本文时,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()实际上是标准化的,其中随着对象扩展…还没有。唯一的问题是浏览器对前者以及未来对后者的支持。

玩一下这里的代码

希望这能有所帮助。

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

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

这并不一定详尽无遗。

传播的语法

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

优点:

如果在没有本机支持的环境中编写代码以执行,则可以只编译此语法(而不是使用polyfill)。(比如巴别塔。) 可以减少复杂性。

缺点:

当这个答案最初被写出来的时候,这是一个建议,没有标准化。在使用提案时,请考虑如果您现在使用提案编写代码,而它没有得到标准化或在向标准化发展的过程中发生变化,该怎么办。这已经在ES2018中标准化了。 字面的,不是动态的。


Object.assign ()

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

优点:

标准化。 动态的。例子: var sources = [{a: " a "}, {b: " b "}, {c: " c "}]; options = Object.assign。应用(对象,({}).concat(来源)); / /或 options =对象。分配({},…来源);

缺点:

更详细的。 如果在没有本机支持的环境中编写代码执行,则需要填充。


这是让我疑惑的承诺。

这和你问的问题没有直接关系。这段代码没有使用Object.assign(),而是使用用户代码(object-assign)来做同样的事情。他们似乎在用Babel编译代码(并将其与Webpack捆绑),这就是我所说的:你可以编译的语法。他们显然更喜欢这样,而不是必须将对象赋值作为一个依赖项包含到他们的构建中。

对象扩展操作符(…)在浏览器中不起作用,因为它还不是任何ES规范的一部分,只是一个建议。唯一的选择是用Babel(或类似的东西)编译它。

正如您所看到的,它只是Object.assign({})上的语法糖。

在我看来,这些是重要的区别。

对象。赋值在大多数浏览器中都有效(不需要编译) ... For对象不是标准化的 ... 防止您意外地改变对象 ... 将填充对象。在没有它的浏览器中赋值 ... 需要更少的代码来表达相同的思想

两者之间有着巨大的差异,并会带来非常严重的后果。投票最多的问题甚至没有触及到这一点,关于物体传播是一项提案的信息在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])
    })
  }