Ihar Krasnik
Ihar Krasnik

Reputation: 2589

How to copy methods from instance of some class to object

Given class

class Test {
  test() {
     console.log('test called');
  }
}

And some object toExtend = {}

How can I extend this object so it will have test method?

Object.assign ( as well as _.extend, _.assign, $.extend) do not copy methods. What is preferable way to do that?

Note that toExtend is passed from outside

UPD: toExtend is instance of another class and has it's own prototype's methods

Upvotes: 2

Views: 179

Answers (1)

ssube
ssube

Reputation: 48277

Object Literals

For object literals, which start with no protoype of their own (Object.getPrototypeOf(toExtend) === Object.protoype)), you can simply use Object.setPrototypeOf to extend the object:

class Test {
  test() {
    console.log('test called');
  }
}

const toExtend = {};

// Set the prototype, so you "inherit" methods:
Object.setPrototypeOf(toExtend, Test.prototype);
toExtend.test();

In older runtimes, you would have to manually assign the prototype:

function Test() {
  // noop ctor
}

Test.prototype.test = function() {
  console.log('test called');
};


var toExtend = {};

// Set the prototype, so you "inherit" methods:
toExtend.__proto__ = Test.prototype;
toExtend.test();

Class Instances

For instances of an existing class, things are significantly more complex. They do have a prototype of their own, potentially with properties that must be copied, so you need to walk through those:

class Foo {
  test() {
    console.log('test');
  }
}

class Bar {
  toast() {
    console.log('toast');
  }
}

function dynamicExtend(target, base) {
  const baseProto = Object.getPrototypeOf(target);
  if (baseProto == Object.prototype) {
    // simple case: no existing prototype
    Object.setPrototypeOf(target, base.prototype);
  } else {
    // complex case: existing prototype
    const proxyClass = class extends base {};
    const proxyProto = proxyClass.prototype;

    // assign the target properties
    Object.getOwnPropertyNames(baseProto).forEach(n => {
      const desc = Object.getOwnPropertyDescriptor(baseProto, n);
      Object.defineProperty(proxyProto, n, desc);
    });

    Object.setPrototypeOf(target, proxyProto);
  }
}

const targets = [{},
  new Bar()
];

targets.forEach(t => {
  dynamicExtend(t, Foo);
  t.test();
  if (t.toast) {
    t.toast();
  }
});

Note that, thanks to the proxy class, this does break instanceof style inheritance checks.

__proto__

As @PatrickRoberts noted in the comments, __proto__ is deprecated, so you should prefer setPrototypeOf whenever possible.

Upvotes: 9

Related Questions