对象传播与 Object.assign

IT技术 javascript ecmascript-6
2021-01-19 19:10:41

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

这两种替代方案的优点/缺点是什么?

使用对象展开

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

或者使用 Object.assign

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

这是让我感到疑惑提交

6个回答

这不一定是详尽无遗的。

传播语法

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作为依赖项包含在他们的构建中。

可能值得注意的是,对象休息传播已移至第 3 阶段,因此将来可能会标准化twitter.com/sebmarkbage/status/781564713750573056
2021-03-19 19:10:41
@Omar 好吧,我想你只是证明了这是一种意见:) 如果有人不承认这种优势,那么是的,其他条件相同时,他们可以使用Object.assign(). 或者您可以手动迭代一组对象及其自己的道具,手动将它们分配给目标并使其更加冗长:P
2021-03-24 19:10:41
正如@JMM 提到的,它现在在 ES2018 规范中 node.green/#ES2018-features-object-rest-spread-properties
2021-03-24 19:10:41
“每个字节都很重要”这就是@yzorg 的最小化器/丑化器
2021-04-03 19:10:41
@JMM 我不确定我看到了“更详细”。作为劣势。
2021-04-07 19:10:41

对于参考对象 rest/spread 在 ECMAScript 2018 中作为第 4 阶段最终确定。该提案可以在这里找到

大多数情况下,对象重置和扩展的工作方式相同,关键区别在于扩展定义了属性,而 Object.assign() 设置了它们这意味着 Object.assign() 会触发 setter。

值得记住的是,除此之外,对象 rest/spread 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() 的工作方式一致,两者都默默地排除空值而没有错误。

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

console.log(z); //{a: 1, b: 2}
这是正确的答案。主要的不同是Object.assign将使用 setter。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}
2021-03-11 19:10:41
这应该是答案……现在是未来。
2021-03-18 19:10:41
谢谢 - 有了这个改变,我明白你的意思了。object spread 不会抱怨 null 对象,它只是跳过它,但是如果尝试传播 null 对象,则数组 spread 会给出 TypeError。
2021-03-19 19:10:41
“未来是现在!” - George Allen明天太晚了。
2021-03-26 19:10:41
以不同方式处理 null 的演示是“苹果和橘子”——不是一个有意义的比较。在数组的情况下,null 是数组的一个元素。在展开的情况下,null 是整个对象。正确的比较是 x 具有null 属性: const x = {c: null};在这种情况下,据我所知,我们将看到的行为就像数组://{a: 1, b: 2, c: null}
2021-04-02 19:10:41

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

编辑:我实际上已经意识到我的例子具有误导性。扩展运算符Object.assign将第一个参数设置为空对象。在我下面的代码示例中,我将错误作为Object.assign调用的第一个参数,因此两者并不等效。的第一个参数Object.assign实际上被修改然后返回,这就是它保留其原型的原因。我在下面添加了另一个示例:

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

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

// What the spread operator desugars into
const errorExtendedUsingImmutableObjectAssign = Object.assign({}, error, {
    someValue: true
});
errorExtendedUsingImmutableObjectAssign instanceof Error; // false

// The error object is modified and returned here so it keeps its prototypes
const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true

另见:https : //github.com/tc39/proposal-object-rest-spread/blob/master/Spread.md

@wortwart 我想区别实际上是扩展运算符不影响源对象(error在这种情况下),因为Object.assign它将修改源对象并返回它。扩展运算符实际上只是 Object.assign 的糖,第一个参数设置为空对象(github.com/tc39/proposal-object-rest-spread/blob/master/...
2021-03-16 19:10:41
@maaartinus 你是对的,我可能措辞不好。我的意思是原型不在复制的对象上。我可能会对其进行编辑以使其更清晰。
2021-03-22 19:10:41
“不会保持原型完整” - 它肯定会,因为它不会修改任何东西。在您的示例中,errorExtendedUsingAssign === error, 但是errorExtendedUsingSpread是一个新对象(并且没有复制原型)。
2021-04-05 19:10:41
以下是一种“浅克隆”对象及其类的方法吗? let target = Object.create(source); Object.assign(target, source);
2021-04-08 19:10:41
@ToolmakerSteve 是的,它将复制所有对象的“自己的属性”,这将有效地成为一个浅层克隆。请参阅:stackoverflow.com/questions/33692912/...
2021-04-09 19:10:41

我想总结一下“扩展对象合并”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 中

工具:节点 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写这篇文章的但我有同样的问题,这是谷歌发给我的。我是 SO 改进长尾任务的奴隶。

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

最后,我会添加此信息作为评论,但我无法在不破坏作者原始意图的情况下对其进行编辑。具体来说,我无法编辑@ChillyPenguin 的评论,而不会失去纠正@RichardSchulte 的意图。但几年后,事实证明理查德是对的(在我看来)。所以我写了这个答案,希望它最终能在旧答案上获得吸引力(可能需要数年时间,但这毕竟是长尾效应的全部内容)。

@LocustHorde 也许我可以将第二段(为什么这个话题很难用谷歌搜索)移到它自己的部分。然后其余的可能适合评论。
2021-03-14 19:10:41
可能不需要您的“为什么回答”部分
2021-03-28 19:10:41
请注意,Safari 将具有以下内容console.log(z):({b: 2, c: 3, d: 4, a: 5}顺序不同)
2021-04-02 19:10:41

注意:Spread 不仅仅是围绕 Object.assign 的语法糖。他们在幕后的运作方式大不相同。

Object.assign 将 setter 应用于新对象,Spread 不会。此外,对象必须是可迭代的。

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

使用它来创建对象的浅表副本 始终将不可变属性设置为复制的良好实践 - 因为可变版本可以传递到不可变属性中,复制将确保您将始终处理不可变对象

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

只有这个答案解决了使用扩展运算符时参考相等性丢失的事实,这太疯狂了,这是这里第二个最不受欢迎的答案。
2021-03-13 19:10:41
为什么说“复制”?这些大胆的标题是什么。当我读到这篇文章时,我觉得我错过了一些背景……
2021-03-14 19:10:41
我想你说“复制”时的意思是“传播”,如果是的话,我理解你的回答:)
2021-03-19 19:10:41