Object.assign() with accessor descriptor

1 minute read

MDN docs:

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 example

 1class Cat {
 2    constructor(name) {
 3        this._name = name;
 4    }
 5
 6    get name() {
 7        return this._name;
 8    }
 9    set name(value) {
10        this._name = value;
11    }
12}
13
14let nyannko = new Cat("nyannko");
15let copy = Object.assign({}, nyannko)
16
17console.log(nyannko.name) // nyannko
18console.log(copy.name) // undefined

The name property is lost.

To copy accessors, we can use Object.getOwnPropertyDescriptor() and Object.defineProperty() as the MDN docs recommend:

 1var obj = {
 2  foo: 1,
 3  get bar() {
 4    return 2;
 5  }
 6};
 7
 8var copy = Object.assign({}, obj); 
 9console.log(copy); 
10// { foo: 1, bar: 2 }, the value of copy.bar is obj.bar's getter's return value.
11
12// This is an assign function that copies full descriptors
13function completeAssign(target, ...sources) {
14  sources.forEach(source => {
15    let descriptors = Object.keys(source).reduce((descriptors, key) => {
16      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
17      return descriptors;
18    }, {});
19    // by default, Object.assign copies enumerable Symbols too
20    Object.getOwnPropertySymbols(source).forEach(sym => {
21      let descriptor = Object.getOwnPropertyDescriptor(source, sym);
22      if (descriptor.enumerable) {
23        descriptors[sym] = descriptor;
24      }
25    });
26    Object.defineProperties(target, descriptors);
27  });
28  return target;
29}
30
31var copy = completeAssign({}, obj);
32console.log(copy);
33// { foo:1, get bar() { return 2 } }

The other way is Object.prototype.__proto__ (but not recommended):

1let completeCopy = Object.assign({__proto__: nyannko.__proto__}, nyannko);
2console.log(completeCopy.name); // nyannko

Object.prototype.__proto__ is deprecated so be aware that this may cease to work at any time. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto