Search code examples
javascriptobjectprototype

Poor Use Case of Object.assign() - Simple Example


I'm reading the MDN docs on Object.assign() and came across one phrase that I don't understand:

The Object.assign() method only copies enumerable and own properties from a source object to a target object. It uses [[Get]] on the source and [[Set]] on the target, so it will invoke getters and setters. Therefore it assigns properties versus just copying or defining new properties. This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters. For copying property definitions, including their enumerability, into prototypes Object.getOwnPropertyDescriptor() and Object.defineProperty() should be used instead.

Particularly this line:

This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters.

I'm not exactly sure what a good example is for advocating against using Object.assign.


Solution

  • A getter is an accessor function for a property that returns the property's value. This is what an object with a getter looks like:

    const obj = {
        get example() {
            console.log("getter was called");
            return Math.floor(Math.random() * 100);
        }
    };
    console.log(obj.example);
    // Note no () ---------^

    Notice that when we read the value of the example property, the function gets run even though it doesn't look like a function call.

    What that part of the MDN docs is saying is that Object.assign will call that getter, it won't create an equivalent getter on the target object. So:

    const obj = {
        get example() {
            console.log("getter was called");
            return Math.floor(Math.random() * 100);
        }
    };
    const obj2 = Object.assign({}, obj); // calls getter
    console.log(obj2.example);           // just has a simple value
    console.log(obj2.example);           // same value, no call
    console.log(obj2.example);           // same value, no call

    obj's example property has a getter, but obj2's example property is just a simple value property. Object.assign didn't copy the getter, it just grabbed the getter's current value and assigned it ot obj2.example.

    You can copy getters, just not with Object.assign:

    function copyProperties(target, source) {
        Object.getOwnPropertyNames(source).forEach(name => {
            Object.defineProperty(
                target,
                name,
                Object.getOwnPropertyDescriptor(source, name)
            );
        });
        return target;
    }
    const obj = {
        get example() {
            console.log("getter was called");
            return Math.floor(Math.random() * 100);
        }
    };
    const obj2 = copyProperties({}, obj); // calls getter
    console.log(obj2.example);            // calls getter
    console.log(obj2.example);            // calls getter
    console.log(obj2.example);            // calls getter

    Of course, if the getter isn't designed to be copied between objects (for instance, if example's getter explicitly used obj), you may get unexpected results.