leo
leo

Reputation: 8520

Object.assign alternative for getters/setters

Object.assign() does not work with getters and setters (because it only reads the property values, as explained in this question):

> let a = {}
> Object.defineProperty(a, "foo", {get: () => "bar"})
> a.foo
'bar'
> let b = Object.assign({}, a)
> b.foo
undefined

What are my alternatives to Object.assign here, if I want to include foo in b?

Upvotes: 1

Views: 1743

Answers (3)

vatz88
vatz88

Reputation: 2452

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.

Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Since you have only set getter for foo and not have any setter it'll not be copied.

Checked out the bellow example, you might find it interesting.

a = {};

Object.defineProperty(a, "foo", {get: () => "bar"});

console.log(a.foo);

a.foo = 1

console.log(a.foo); // logs bar and not 1

Solution

Add a setter too!

var a = {};

(function(){
  var fooValue = 'bar';

  Object.defineProperty(a, "foo", {
    get: () => {return fooValue},
    set: (newFoo)=>{fooValue = newFoo},
    enumerable: true // Defaults to 'false', which will not copy the property
  });
})();

var b = Object.assign({}, a);

console.log(a, b);

Note: This will copy the property as is without the setter or getter method. If you have some custom logic in setter or getter it'd not work in the copy.

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386604

You need to capture setters and getter directly, because

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 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, use Object.getOwnPropertyDescriptor() and Object.defineProperty() instead. [source; emp: ns]

and take some code from here.

// 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;
}

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

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


copy = completeAssign({}, obj);
console.log(copy);
// { foo:1, get bar() { return 2 } }
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

mbojko
mbojko

Reputation: 14679

You can Object.create and use prototypal inheritance:

let a = {};
Object.defineProperty(a, "foo", { get: () => "foo" });
console.log(a.foo);
let b = Object.create(a);
console.log(b.foo);

But then mutations a (if it can have other properties besides foo) will be automatically reflected in b, I don't know whether that's the desired behavior. Maybe doing

let b = Object.create(a);
Object.assign(b, a);

will solve both problems.

Upvotes: 2

Related Questions