9

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.

qarthandso
  • 2,100
  • 2
  • 24
  • 40

1 Answers1

9

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:

var 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:

var obj = {
    get example() {
        console.log("getter was called");
        return Math.floor(Math.random() * 100);
    }
};
var 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;
}
var obj = {
    get example() {
        console.log("getter was called");
        return Math.floor(Math.random() * 100);
    }
};
var 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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • This makes so much sense, thank you. But what if I've added specific non-enumerable properties to `source` object and I'm using your last example with `getOwnPropertyNames`? Even those we're using this method to get the non-enumerable getters and setters, it'll also pick up any other non-enumerables (such as `length` if it's an array object). So I'd have to exclude them manually in the custom `copyProperties` method, yes? – qarthandso Aug 19 '17 at 13:59
  • 1
    @qarthandso: I just used `getOwnPropertyNames` almost at random, figuring if someone wanted to copy getters and setters and such, they probably would want to copy non-enumerable properties as well. (By that logic,I probably should have included `getOwnPropertySymbols`, too; but it was just a small example.) If you want to copy properties but stick to enumerable ones, use `Object.keys` which only gives you own, enumerable properties. – T.J. Crowder Aug 19 '17 at 14:01