对象传播与Object.assign


396

假设我有一个options变量,并且想要设置一些默认值。

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

使用对象传播

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

或使用Object.assign

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

这就是让我感到奇怪的承诺


4
好吧,第一个是提议的新语法,它不是ES6的一部分,因此它取决于您要遵循的标准。
loganfsmyth,2015年

5
定义“ 最佳 ”(小心,不要以基于意见的问题结尾:-)
Amit 2015年

1
如果在没有本机支持的环境中运行,还可能取决于您希望如何支持它。您也许可以编译语法。您可能需要填充的对象或方法。
JMM 2015年

6
除了兼容性问题之外,Object.assign可以对原始对象进行突变,这很有用。传播不能。
pstanton

2
为了澄清@pstanton的注释-object.assign可以修改现有的目标对象(从源覆盖属性,而其他属性保持不变);它不会碰到对象。我首先将他的“原始对象”读为“源对象”,所以将本注释写给其他同样误读它的人。:)
ToolmakerSteve

Answers:


328

这并不一定是详尽无遗的。

传播语法

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

优点:

  • 如果编写代码以在没有本机支持的环境中执行,则可以仅编译此语法(与使用polyfill相对)。(例如,使用Babel。)

  • 不太冗长。

缺点:

  • 最初编写此答案时,这只是一个建议,而不是标准化的。在使用投标时,请考虑一下如果现在使用它编写代码会做什么,并且在朝着标准化的方向发展时,它不会变得标准化或更改。此后已在ES2018中标准化。

  • 从字面上看,不是动态的。


Object.assign()

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

优点:

  • 标准化。

  • 动态。例:

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    // or
    options = Object.assign({}, ...sources);

缺点:

  • 更详细。
  • 如果创作代码以在没有本机支持的环境中执行,则需要polyfill。

这就是让我感到奇怪的承诺。

这与您的要求没有直接关系。该代码未使用Object.assign(),它正在使用object-assign执行相同操作的用户代码()。他们似乎正在使用Babel编译该代码(并将其与Webpack捆绑在一起),这就是我所说的:您可以编译的语法。他们显然更喜欢将其包含object-assign在其构建中作为依赖项。


15
这可能是值得指出的是,其余对象传播已经移到第3阶段等都将可能在未来被标准化twitter.com/sebmarkbage/status/781564713750573056
williaster

11
@JMM我不确定我是否看到“更详细”。作为缺点。
DiverseAndRemote.com

6
@Omar好吧,我想您只是证明了这是一种意见:)如果某人没有意识到这种优势,那么是的,其他条件相同的人都可以使用Object.assign()。或者,您可以手动遍历对象的数组,并手动将它们自己的道具手动分配给目标,并使它更加冗长:P
JMM

7
正如@JMM所提到的,它现在位于ES2018规范 node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.

2
“每个字节都计数”那就是@yzorg最小化/ uglify的含义
DiverseAndRemote.com 18-03-31

171

作为参考,对象的休息/传播在ECMAScript 2018中最终确定为阶段4。可以在此处找到建议。

大多数情况下,对象重置和散布都以相同的方式进行,主要区别在于散布定义属性,而Object.assign()设置属性。这意味着Object.assign()触发设置器。

值得记住的是,除此之外,对象休息/传播1:1映射到Object.assign()并以不同的方式作用于数组(可迭代)传播。例如,在扩展数组时,将扩展空值。但是,使用对象扩展时,空值会默默地扩展为零。

数组(可迭代)传播示例

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()的工作方式一致,两者均无提示地无提示地排除了null值。

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

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

10
这应该是选定的答案。
Evan Plaice

4
这应该是答案...现在是未来。
扎卡里·阿布雷施

1
这是正确的答案。主要的不同是Object.assign将使用二传手。Object.assign({set a(v){this.b=v}, b:2}, {a:4}); // {b: 4}主场迎战{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}
David Boho '18

1
“未来是现在!” - 明天的乔治·艾伦George Allen Tomorrow)为时已晚。
鲁芬

1
对null进行不同处理的示例是“苹果和橙子”-毫无意义的比较。在数组的情况下,null是数组的元素。在扩展情况下,整个对象为null。正确的比较是让x具有null属性const x = {c: null};。在这种情况下,对于AFAIK,我们将看到类似于数组的行为://{a: 1, b: 2, c: null}
制造商史蒂夫

39

我认为,散布运算符与Object.assign当前答案中似乎没有提到的一个大区别是,散布运算符不会将源对象的原型复制到目标对象。如果要向对象添加属性,并且不想更改对象的实例,则必须使用Object.assign。下面的示例应证明这一点:

const error = new Error();
error instanceof Error // true

const errorExtendedUsingSpread = {
  ...error,
  ...{
    someValue: true
  }
};
errorExtendedUsingSpread instanceof Error; // false

const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true


“不会使原型完好无损” -它肯定会,因为它不会修改任何内容。在您的示例中errorExtendedUsingAssign === error,,但是errorExtendedUsingSpread是一个新对象(并且未复制原型)。
maaartinus

2
@maaartinus你说得对,我可能措辞很不好。我的意思是原型不在复制的对象上。我可能会对其进行更清晰的编辑。
肖恩道森

以下是“浅克隆”对象及其类的方法吗?let target = Object.create(source); Object.assign(target, source);
制造商史蒂夫,

@ToolmakerSteve是的,它将复制对象的所有“自有属性”,并有效地将其作为浅表克隆。参见:stackoverflow.com/questions/33692912/…–
肖恩·道森

12

正如其他人提到的那样,在撰写本文时,它Object.assign()需要一个...polyfill,而对象散布则需要一些转堆(也许还有一个polyfill)才能起作用。

考虑以下代码:

// 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()实际上是标准化的,因为...还没有对象传播。唯一的问题是浏览器支持前者,将来也支持后者。

在这里玩代码

希望这可以帮助。


1
谢谢!对于我的上下文,您的代码示例使决定变得非常容易。通过在内联代码中包含pollyfill,转译器(Babel或Typescript)使Spread运算符与浏览器更加兼容。只是感兴趣的打字稿transpiled版本几乎是一样的巴贝尔:typescriptlang.org/play/...
马克Whitfeld

2
嗯...您的两个案例不会产生相同的结果吗?在第一种情况下,您是将属性从一个对象复制到另一个对象,而在另一种情况下,则是创建一个新对象。Object.assign返回目标,因此在第一种情况下objAss和newObjAss相同。
凯文B

添加新的第一个参数{}应该可以解决不一致问题。
凯文B

11

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

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

据我所知,这些是重要的区别。

  • Object.assign在大多数浏览器中均可运行(无需编译)
  • ... 对于对象没有标准化
  • ... 保护您免于意外改变对象
  • ... 没有它会在浏览器中填充Object.assign
  • ... 需要更少的代码来表达相同的想法

34
不是的语法糖Object.assign,因为传播操作符将始终为您提供一个新对象。
MaxArt 2016年

4
实际上,我很惊讶其他人没有更多地强调可变性差异。想想所有开发人员
花费的时间,都无法

现在大多数现代浏览器都支持此功能(与其他ES6一样): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991

11

我想通过工具总结“扩展对象合并” ES功能在浏览器和生态系统中的状态。

规格

浏览器:即将在Chrome,SF,Firefox中运行(版本60,IIUC)

  • 浏览器支持Chrome 60附带的 “扩展属性” ,包括这种情况。
  • 在当前的Firefox(59)中不支持这种情况,但在我的Firefox Developer Edition中可以使用。因此,我相信它将在Firefox 60中发布。
  • Safari:未经测试,但Kangax表示它可以在Desktop Safari 11.1中使用,但不适用于SF 11
  • iOS Safari:未受限制,但Kangax表示它可在iOS 11.3中使用,但不适用于iOS 11
  • 还没有在Edge中

工具:Node 8.7,TS 2.1

链接

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

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+)中无需转换即可工作。

为什么回答?

我写这2.5 在提出原始问题后2.。但是我也有同样的问题,这就是Google发送给我的地方。我是SO改善长尾巴任务的奴隶。

由于这是“数组传播”语法的扩展,因此我发现很难用Google搜索,也很难在兼容性表中找到。我能找到的最接近的是Kangax“ property spread”,但是该测试在同一表达式中没有两个价差(不是合并)。此外,投标/草稿/浏览器状态页面中的名称均使用“属性传播”,但在我看来,这是社区在提议使用传播语法进行“对象合并”之后到达的“第一主体”。(这可能会解释为什么Google这么难的原因。)因此,我在这里记录我的发现,以便其他人可以查看,更新和编译有关此特定功能的链接。我希望它能流行起来。请帮助在规范和浏览器中传播有关它着陆的消息。

最后,我会将此信息添加为评论,但是如果不破坏作者的初衷,就无法对其进行编辑。具体来说,我不能编辑@ChillyPenguin的评论,除非它失去他纠正@RichardSchulte的意图。但是几年后,理查德证明是正确的(我认为)。因此,我改写了这个答案,希望它最终会在旧的答案上引起关注(可能要花几年时间,但这毕竟是长尾效应的全部内容)。


3
您可能不需要“为什么回答”部分
LocustHorde

@LocustHorde也许我可以将第二段(为什么这个主题很难用Google搜索)移到它自己的部分。然后其余的内容可能会成为评论。
yzorg

8

注意:传播不只是Object.assign周围的语法糖。他们在幕后的运作方式大不相同。

Object.assign将设置器应用于新对象,Spread则不然。另外,对象必须是可迭代的。

复制 如果您现在需要该对象的值,并且不希望该值反映该对象的其他所有者所做的任何更改,请使用此值。

使用它创建对象的浅表副本的最佳做法是始终将不可变属性设置为复制-因为可变版本可以传递给不可变属性,所以复制将确保您将始终处理不可变对象

分配 分配与复制有点相反。Assign将生成一个setter,该setter会将值直接分配给实例变量,而不是复制或保留它。调用assign属性的getter时,它将返回对实际数据的引用。


3
为什么说“复制”?这些大胆的标题是什么。我读这篇文章时感觉好像错过了一些背景...
ADJenks,

2

其他答案很旧,无法获得很好的答案。
下面的示例是针对对象文字的,它可以帮助它们如何相互补充,以及如何不能相互补充(因此有所不同):

var obj1 = { a: 1,  b: { b1: 1, b2: 'b2value', b3: 'b3value' } };

// overwrite parts of b key
var obj2 = {
      b: {
        ...obj1.b,
        b1: 2
      }
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}}  // NOTE: b2,b3 still exists

// overwrite whole of b key
var obj3 = {
      b: {
        b1: 2
      }
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
  // res3: {"a":1,"b":{"b1":2}}  // NOTE: b2,b3 values are lost

这里还有一些关于数组和对象的小例子:https :
//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax


1

现在这是ES6的一部分,因此已经标准化,并且还记录在MDN上: https //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

它非常方便使用,并且与对象分解一起意义重大。

上面列出的一个剩余优点是Object.assign()的动态功能,但是,这与将数组扩展到文字对象内部一样容易。在编译的babel输出中,它完全使用Object.assign()演示的内容

因此正确的答案是使用对象散布,因为它现在已经标准化,被广泛使用(请参阅react,redux等),易于使用并且具有Object.assign()的所有功能。


2
不,它不是ES6的一部分。您提供的链接仅指传播运算符在数组上的使用。如JMM的回答所述,在对象上使用散布运算符目前是第2阶段(即草案)的提议。
ChillyPenguin

1
我认为我从未见过如此完全基于错误信息的答案。即使一年后,如果ES规范也不受支持,并且在大多数环境中也不支持。
3ocene

Chilly和3o证明是错误的,Richard是。浏览器支持和工具支持都在登陆,但在理查德回答后花了1.5年。请参阅我的新答案以获取截至2018
。– yzorg

0

当您必须使用Object.assign时,我想添加一个简单的示例。

class SomeClass {
  constructor() {
    this.someValue = 'some value';
  }

  someMethod() {
    console.log('some action');
  }
}


const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok

const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!

使用JavaScript时不清楚。但是如果您想创建某个类的实例,使用TypeScript会更容易

const spread: SomeClass = {...new SomeClass()} // Error
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.