Search code examples
javascriptcssobjectdomassign

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


Context

When using vanilla js to update a DOM element's style attribute, why does object spread fail to update whilst Object.assign succeeds?

E.g., in the included code snippet, objectAssignDirect and objectAssignIndirect correctly set background-color whilst objectSpread incorrectly resets the result div's background-color.

Questions

  1. Why does this happen? (is this due to cloning issues or properties such as inherited properties not being copied?)
  2. Is there a way to replicate Object.assign's desired behaviour with object spread?

References

There are several discussions comparing Object.assign and object spread but none seem to address this strange behaviour:

// 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>


Solution

  • Why does this happen?

    style is a read-only property, and cannot be assigned to a new value.

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

    Creates a shallow copy of element.style, adds/updates the property backgroundColor and assigns the copy back to element.style. For this reason it fails, because you cannot assign element.style a new value.

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

    Assigns each property/value pair in the second argument to element.style. It does not create a copy of element.style, but mutates the element.style object.

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

    No, object spread is used only in object literals, which will always result in a new object being created. You cannot update an existing object using object spread.