Files
scottyah-blog/hugo-content/themes/cactus/exampleSite/content/posts/2019-03-08-Object-assign-with-accessor-descriptor.md
2022-07-31 17:32:45 -07:00

2.6 KiB

title, date, tags, category, keywords
title date tags category keywords
Object.assign() with accessor descriptor 2019-03-08 09:00:00
JavaScript
tech
Javascript
ES2015
ES6

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

class Cat {
    constructor(name) {
        this._name = name;
    }

    get name() {
        return this._name;
    }
    set name(value) {
        this._name = value;
    }
}

let nyannko = new Cat("nyannko");
let copy = Object.assign({}, nyannko)

console.log(nyannko.name) // nyannko
console.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:

var obj = {
  foo: 1,
  get bar() {
    return 2;
  }
};

var copy = Object.assign({}, obj); 
console.log(copy); 
// { foo: 1, bar: 2 }, the value of copy.bar is obj.bar's getter's return value.

// This is an assign function that copies full descriptors
function completeAssign(target, ...sources) {
  sources.forEach(source => {
    let descriptors = Object.keys(source).reduce((descriptors, key) => {
      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
      return descriptors;
    }, {});
    // by default, Object.assign copies enumerable Symbols too
    Object.getOwnPropertySymbols(source).forEach(sym => {
      let descriptor = Object.getOwnPropertyDescriptor(source, sym);
      if (descriptor.enumerable) {
        descriptors[sym] = descriptor;
      }
    });
    Object.defineProperties(target, descriptors);
  });
  return target;
}

var copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }

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

let completeCopy = Object.assign({__proto__: nyannko.__proto__}, nyannko);
console.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