0

Is it possible to dynamically append to the prototype of an object's instance? For example, I have two dell objects, one's a laptop and one's a desktop. I want to be able to create two instances of dell and then extend the prototype to either laptop or desktop so I can use the appropriate getters / setters.

JSFiddle link: http://jsfiddle.net/QqXgV/1/

var dell = function(){}
dell.prototype = new computer();
dell.prototype.constructor = dell;

var hp = function(){}
hp.prototype = new computer();
hp.prototype.constructor = hp;

var computer = function(){}
computer.prototype.setType = function(type){
  //Here is where I would extend.
  this.prototype extends window[type].prototype;
}

var laptop = function(){}
laptop.prototype.getKeyboard = function(){
  return "motherboard";
}

var desktop = function(){}
desktop.prototype.getKeyboard = function(){
  return "usb";
}

var dellDesktop = new dell();
dellDesktop.setType("desktop");

var dellLaptop = new dell();
dellLaptop.setType("laptop");

//This is the end goal.
dellDesktop.getKeyboard(); //Returns usb
dellLaptop.getKeyboard(); //Returns motherboard
//And then be able to do the same thing with hp.
  • Using function expressions order is important. Does your code work? – elclanrs Jan 06 '14 at 00:19
  • I just added a working JSFiddle link near the top. –  Jan 06 '14 at 00:31
  • Well, yes it works, but it's not the same exact code as posted here. In the fiddle `computer` is declared first, that's why it works; that's what I meant by "order". – elclanrs Jan 06 '14 at 00:34
  • One the JSFiddle page, the method setType is where I'm having trouble. I appended the methods from desktop into this.__proto__, they showed up in both the desktop and laptop instances. –  Jan 06 '14 at 00:39

2 Answers2

2

In a case where a Dell can be either a Laptop or a Desktop one might create a DellLaptop and a DellDesktop constructor. But Laptops come in ultrabooks and netbooks so one has to create DellLaptopUltrabook and DellLaptopNetBook. So in a case where something can be either this or that and other objects can be this or that (like HP can also be either Laptop or Desktop) maybe the following pattern can help out:

var Computer = function(){};
//proxy getKeyboard based on what type it is
Computer.prototype.getKeyboard = function(){
  return this.type.getKeyboard.call(this);
};

var Dell = function(args){
  //do some checks on args here
  //re use Computer constructor
  Computer.call(this.args);
  //re use laptop or Desktop constructor code
  args.type.call(this,args);
  //re use Laptop or Desktop protype members
  this.type=args.type.prototype;

};
Dell.prototype = Object.create(Computer.prototype);
Dell.prototype.constructor = Dell;

var Laptop = function(){};
Laptop.prototype.getKeyboard = function(){
  return "motherboard";
};
//ultrabook and netbook can inherit from Laptop
//  so you could pass UltraBook or Netbook as type


var Desktop = function(){};
Desktop.prototype.getKeyboard = function(){
  return "usb";
};

var dellDesktop = new Dell({type:Desktop});

var dellLaptop = new Dell({type:Laptop});

//This is the end goal.
console.log(dellDesktop.getKeyboard()); //Returns usb
console.log(dellLaptop.getKeyboard()); //Returns motherboard
HMR
  • 37,593
  • 24
  • 91
  • 160
  • How does the Object.create line of code behave differently than the new line of code? Dell.prototype = Object.create(Computer.prototype); dell.prototype = new computer(); –  Jan 06 '14 at 12:09
  • 2
    @Josh You should not create a instance of Parent to set the prototype part of inheritance in Child This because instance specific members of Parent become part of Child through prototype but are not instance specific (they are shared for all children), best case is that these members are immediately shadowed Another problem can be when you can't create an instance of Parent without mocking a lot of constructor parameters. Object.create (with polifyl for olther browsers) would allow you to set the prototype part without creating an instance of Parent. http://stackoverflow.com/a/16063711/1641941 – HMR Jan 06 '14 at 12:53
  • 1
    @josh You can't set Dell to get prototype of Laptop or Desktop because prototype is shared among instances created by the Dell constructor. If I create a Dell laptop instance named dLapt, then create a Dell desktop instance named dDesk, change the prototype of Dell to inherit from Desktop instead of Laptop then dLapt would change to inherit from Desktop as well. Prototype is shared and not instance specific. I suggest reading at least the introduction of the link I posted in my previous comment. – HMR Jan 06 '14 at 12:56
  • Thanks for the explanation. Would you recommend creating separate constructors such as DellLaptopUltrabook and DellLaptopNetBook rather than Dell with an object of type? If so, could you explain the benefits? Thanks –  Jan 07 '14 at 01:58
  • @Josh I'd recommend code used in the answer because you can combine Dell/HP constructor with Ultrabook, NetBook or Laptop where you can have Ultrabook and Netbook inherit from Laptop. – HMR Jan 07 '14 at 11:38
  • While this is generally good programming practice, it also doesn't answer the original question. How _does_ one change the prototype of some object _instance_ (without changing the object's prototype binding. Just for that one instance). While incredibly rare, low level libraries may need to do this, and if whoever's working on one finds this answer, they're still none the wiser =) – Mike 'Pomax' Kamermans Jan 06 '22 at 22:45
1

While the answer by HMR is the better practice, if you absolutely need to change a running instance's prototype from one object to another, use the Object.setPrototypeOf function:

let X = class {
  constructor() { this.v = 10; }
  test() { console.log(this.v); }
  morph(T) { Object.setPrototypeOf(this, T.prototype); }
};

let Y = class extends X {
  constructor() { super(); }
  test() { super.test(); console.log(10 * this.v); }
};

let Z = class { test() { console.log(34 * this.v); }};

let x = new X();

console.log(`plain test`);
x.test();

x.morph(Y);
console.log(`rewriten test 1`);
console.log(`x instanceof X?:`, x instanceof X);
console.log(`x instanceof Y?:`, x instanceof Y);
console.log(`x instanceof Z?:`, x instanceof Z);
console.log(`test result:`);
x.test();

x.morph(Z);
console.log(`rewriten test 2`);
console.log(`x instanceof X?:`, x instanceof X);
console.log(`x instanceof Y?:`, x instanceof Y);
console.log(`x instanceof Z?:`, x instanceof Z);
console.log(`test result:`);
x.test();

And note that this can even be done while in the constructor, which you should be even less tempted to do. But you can.

let X = class {
  constructor() {
    this.v = 10;
    Object.setPrototypeOf(this, Y.prototype);
  }
  test() { console.log(this.v); }
};

let Y = class extends X {
  test() { console.log(10 * this.v); }
};

let x = new X();
console.log(`x is an instance of Y:`, x instanceof Y);

And to answer the "when would you ever need this?" You might need this if you're working with, say, a polymorphic object such as a variable dimensional geometry, in which you want an intuitive constructor new Geometry(inputs) that rebinds its prototype based on the inputs, with the same API but wildly different implementations. Could you do that by telling users that they need their own code to figure out which specific constructor they need to use? Sure. But that'd be a very unpleasant experience for the user, for whom the implementation details are irrelevant, and it's that original constructor name that matters.

Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153