为什么在更新 DOM 元素的样式属性时对象传播失败但 Object.assign 成功?

Why does object spread fail but Object.assign succeeds when updating a DOM element's style attribute?

上下文

当使用 vanilla js 更新 DOM 元素的 style 属性时,为什么对象传播失败而 Object.assign 成功?

例如,在包含的代码片段中,objectAssignDirectobjectAssignIndirect 正确设置了 background-colorobjectSpread 错误地重置了结果 divbackground-color.

问题

  1. 为什么会这样? (这是由于克隆问题还是继承属性等属性没有被复制?)
  2. 有没有办法通过对象传播来复制 Object.assign 所需的行为?

参考资料

有几个比较 Object.assign 和对象传播的讨论,但 none 似乎解决了这种奇怪的行为:

// Using `Object.assign` directly.
const objectAssignDirect = () => { 
  Object.assign(document.querySelector('.myClass').style, { backgroundColor: 'red' }); // Works.
  console.log('Result should be red');
}

// Updating using `Object.assign` with variable.
const objectAssignIndirect = () => {
  const myElement = document.querySelector('.myClass')
  Object.assign(myElement.style, { backgroundColor: 'blue' }); // Works.
  console.log('Result should be blue');
}

// Using object spread with variable.
const objectSpread = () => {
  const myElement = document.querySelector('.myClass')
  myElement.style = { ...myElement.style, backgroundColor: 'green' }; // Fails.
  console.log('Result should be green');
}
body {
  font-size: 2em;
}

.myClass {
  width: 100%;
  height: 50px;
  background-color: black;
  color: white;
  border: 4px solid black;
  text-align: center;
  padding: 10px;
}

button {
  padding: 15px;
  margin: 30px;
  color: white;
  border-radius: 20px;
}

.red {
  background-color: red;
}
.blue {
  background-color: blue;
}
.green {
  background-color: green;
}
<div style="display:flex;justify-content:center;">
  <button class="red" onclick="objectAssignDirect();">Use <code>Object.assign</code> directly</button>
  <button class="blue" onclick="objectAssignIndirect();">Use <code>Object.assign</code> indirectly</button>
  <button class="green" onclick="objectSpread();">Use object spread</button>
</div>

<div class="myClass">Result</div>

直接分配给元素的 .style 标签不会导致样式改变。相反,它会默默地失败:

foo.style = { backgroundColor: 'green' };
console.log(foo.style.backgroundColor);
<div id="foo">foo</div>

您必须分配给现有 .style(现有 CSSStyleDeclaration)的 属性 才能调用导致 DOM 更改的 setter。您不能尝试用其他东西完全重新分配 style 属性,因为这样您就不会在 CSSStyleDeclaration 上调用特殊的 setter。执行 { ...myElement.style } 会创建一个新的普通对象,而不是 CSSStyleDeclaration。 Object.assign 适用于您的两个原始代码段,因为它会调用 setters.

这种行为的另一个例子:

const obj = Object.create({ style: {
  set someProp(arg) {
    console.log('Setter invoked; changing DOM');
  }
}});

console.log('Invokes setter:');
obj.style.someProp = 'newVal';
console.log("Doesn't invoke setter:");
obj.style = { someProp: 'newVal' };

Why does this happen?

style 是只读的 属性,不能赋新值。

element.style = { ...element.style, backgroundColor: 'green' };

创建 element.style、adds/updates 和 属性 backgroundColor 的浅表副本并将副本分配回 element.style。由于这个原因它失败了,因为你不能分配 element.style 一个新值。

Object.assign(element.style, { backgroundColor: 'green' });

将第二个参数中的每个 property/value 对分配给 element.style。它不会创建 element.style 的副本,但会改变 element.style 对象。

Is there a way to replicate Object.assign's desired behaviour with object spread?

不,对象扩展仅用于对象文字,这将始终导致创建新对象。您不能使用对象传播更新现有对象。