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

2020-08-26 11:20发布

问题:

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.

回答1:

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.