Null Object spread horror story

javascriptwebdevprogramming
Sunday November 2021
Updated at:
07/25/2021
18
3'

While most of the modern frontend engineers use object spread syntax a lot in their code, we all overcome some simple details and underlying mechanisms of how it actually works.

With a first look, this code looks like something that would break right?

/*
object? = {...123} // => {} Wait what? Is this valid??
object? = {...undefined} // => {} Um, wat?
object? = {...null} => // {} Please stop
object? = {...false} => // {} Ok I'm done, bye javascript
object? = {...'Smallpdf'} // => {0: "S", 1: "m", 2: "a", 3: "l", 4: "l", 5: "p", 6: "d", 7: "f"}
*/

// Did we break javascript??!

Probably we would expect a TypeError here. But we must not forget that ... is a syntax code, not an operator. So the result of it depends on the surrounding context. It behaves differently if it's in an array ([...myArr]), in an object ({...myObj}), or in a function argument list (myFunc(arg1, ..restArgs)

So let's see what happens exactly when it is used inside an object.

According to TC39, object spread initializer is a syntactic sugar on top of Object.assign. So the next logical step is to see how the Object.assign should work, as instructed by the ECMAscript spec.

Screenshot from ECMAScript spec explaining how Object.assign should work

In our case, when using the {...something} syntax, the object expression ({}) is the target so it's a newly created object and sources is whatever we pass after the ... syntax, so in our case it's something

Now if something is null or undefined we can see an explicit instruction of how Object.assign should handle this, treat it like an empty List so our end result will just ignore it. This explains why {...undefined} and {...null} returns an empty object and doesn't crash in any way.

But what happens with false 123 and 'Smallpdf'? Let's go back to the ECMAscript spec

After explicitly handling undefined and null cases it concludes with the next steps:

Screenshot from ECMAScript spec explaining what should happen when value is null or undefined

So we see that for other types of arguments, (except null or undefined) the spec uses the ToObject abstract operation, to convert the value to an object and if the return value is not undefined it will try to use the enumerable properties of the result. Keep in mind that ToObject conversions are described in the table below:

Screenshot from ECMAScript spec with a detailed table explaining what ToObject conversion should do depending on the argument type

If we try to code this we will get the following results:

// ToObject conversion
const NumberObject = new Number(123);
const BooleanObject = new Boolean(false);
const StringObject = new String('Smallpdf');

// Get properties for each items, and return enumerable properties to our object

Object.getOwnPropertyDescriptors(NumberObject)
// => {}
// So object? = {...123} => {} makes sense

Object.getOwnPropertyDescriptors(BooleanObject)
// => {}
// object? = {...false} => {} yup

Object.getOwnPropertyDescriptors(StringObject)
/* =>
0: {value: "S", writable: false, enumerable: true, configurable: false}
1: {value: "m", writable: false, enumerable: true, configurable: false}
2: {value: "a", writable: false, enumerable: true, configurable: false}
3: {value: "l", writable: false, enumerable: true, configurable: false}
4: {value: "l", writable: false, enumerable: true, configurable: false}
5: {value: "p", writable: false, enumerable: true, configurable: false}
6: {value: "d", writable: false, enumerable: true, configurable: false}
7: {value: "f", writable: false, enumerable: true, configurable: false}
length: {value: 8, writable: false, enumerable: false, configurable: false}

*/

// So according to the spec, we take only the `enumerable: true` properties
// from this object. Finally we use their `keys` (0, 1, 2, 3, 4, 5, 6, 7)
and their `value` ('S', 'm', 'a', 'l', 'l', 'p', 'd', 'f') and add them
into our new object.

// object? = {...'Smallpdf'} // => {0: "S", 1: "m", 2: "a", 3: "l", 4: "l", 5: "p", 6: "d", 7: "f"}
// it all makes sense now

Javascript surely is weird, but if we follow the spec, it all makes sense! 🌈 🎉