4

I'm trying to create helper method for constructor inheritance in JavaScript.

I have the following simple code:

function extend(parent, fn) {
    return function () {
        parent.call(this);
        fn.call(this);
        fn.prototype.constructor = parent;
    }
}

function foo() {
    var a = 1;
    this.getA = function () { return a; };
}

var bar = extend(foo, function () {
    var b = 2;
    this.getB = function () { return b; };
  });

var obj = new bar();
console.log(obj.getA()); //1
console.log(obj.getB()); //2

console.log(obj instanceof bar); //true
console.log(obj instanceof foo); //false

1) How to make the last line (obj instanceof foo) to be true?

2) What do you think about such solution, is it too much bad? Does exists other options to achieve something similar?

http://jsfiddle.net/jC6uz/

EDIT: I ended up with the following "extend" implementation:

function extend(parent, fn) {
    var Class = function () {
        parent.call(this);
        fn.call(this);
    }
    Class.prototype = parent.prototype;
    Class.prototype.constructor = Class;
    return Class;
}

But now, how to return "bar" when called obj.constructor.name?
http://jsfiddle.net/jC6uz/1/

EDIT 2: Another version that supports constructor parameters (not straight forward, but still possible) http://jsfiddle.net/jC6uz/3/

Alex Dn
  • 5,465
  • 7
  • 41
  • 79
  • For question 1), see here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof (lines 14-18 in first code block) for clues. – jfriend00 Nov 04 '13 at 21:20
  • @jfriend00 I tried, but then "foo" is executed twice, is it possible to set the "constructor" without instantiate "foo" first? – Alex Dn Nov 04 '13 at 21:29
  • When you do it the way they do it in the MDN article, you don't explicitly call the ancestors constructor because it is already called for you. The article makes it very clear how `instanceof` works. It searches through the prototype chain. If you want `instanceof foo` to work, then it has to be in the prototype chain. – jfriend00 Nov 04 '13 at 21:33
  • @jfriend00 right, but when I write "new foo()" when assigning to prototype, it immediately execute. I solved it by using 'foo.prototype' instead of 'new foo()'. Now I'm trying to find a way to return "bar" when calling obj.constructor.name :) Hope this possible. – Alex Dn Nov 04 '13 at 21:46

2 Answers2

2

Per the MDN article in my comment, this is how you would do it using their technique, but fit into your style:

function extend(parent, fn) {
    var newConstructor = function() {
        fn.call(this);
    }
    newConstructor.prototype = new parent();
    return newConstructor;
}

function foo() {
    var a = 1;
    this.getA = function () { return a; };
}

var bar = extend(foo, function () {
    var b = 2;
    this.getB = function () { return b; };
});

var obj = new bar();
console.log(obj.getA()); //1
console.log(obj.getB()); //2

console.log(obj instanceof bar); //true
console.log(obj instanceof foo); //true

Working demo: http://jsfiddle.net/jfriend00/efReT/


One thing your particular implementation does not allow for is for the constructor to have arguments.

FYI, I personally dislike using the word Class in javascript. Once you really "get" how javascript works, it doesn't really have a class concept. It has objects and prototypes and I think it's ultimately cleaner to not confuse or equate either of those with the different concept of a class. When I first came to JS from C++, I tried to find parallels in JS so I could write like I used to write C++, but that isn't really what you want to do. You're better off learning how JS does things and coding within the strengths of that than trying to create analogs to some other language that aren't really how it works. That's why I choose a word like newConstructor instead of Class in my implementation, because it's more descriptive for what JS is really doing and how it really works.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Now I understand what you mean, and "foo" really called once, but I like it less, because even if it called once, it called when I execute "extend" and not when using "new". In addition, as you stated, this way not allow constructor arguments. I made a little "upgrade" to my solution, to support constructor arguments. It not a straight forward solution, but at least it gives an option to set "base" constructor arguments during the instantiation of "bar". Please see new fiddle: http://jsfiddle.net/jC6uz/2/ – Alex Dn Nov 05 '13 at 08:02
  • @AlexDn - But this is one way to use the prototype and allow it to help you make things more efficient. If you want arguments, you do need to go a different way. FYI, I'm starting to really like mixins much more than inheritance in javascript. It doesn't solve the instanceof issue (which doesn't bother me because I'd rather use polymorphism than test a type), but it seems a lot simpler than trying to simulate inheritance in a language that wasn't really built for it. – jfriend00 Nov 05 '13 at 08:05
  • Yes, I know using prototypes is more efficient for performance and memory, so in cases when performance matters, my solution not really good. Currently, I don't see my project will need thousands of instances of same "Class/Constructor", so it less important right now for me. What is important that I can use "private" variables and make kind of encapsulation. Together with this, the ability to pass arguments to base, is a very nice option :) Thank you for pointing me to solution! My next step is to implement "super" for methods created in constructor that overrides base methods. – Alex Dn Nov 05 '13 at 08:13
1

If a function is a constructor it's best to start it with a upper case. The function named foo does not identify itself as a constructor so it's best to name it Foo.

When you set the prototype of Child you don't know the instance so it's not possible to call Parent.apply(this,arguements);. If you know that Child is a Parent then you can add the Parent.appy in the body of Child (see link at the end).

It looks like you want to dynamically extend one constuctor with another, the most complete way (instanceof and constructor are both working as expected and Parent instance members are the Child's instance members) would be to have extend create the instance for you or add an init function that initialises "Parent" instance members.

If you're looking for something like implements or multiple inheritance that is used for situations where an object can do something instead of is something check out the link in the end for a mix in pattern.

var extend = function(source,target,arg){
  //source=Parent, target=Child
  //set prototype without createing a source
  //instance and without using Object.create
  var fn=function(){},ret;
  fn.prototype = source.prototype;
  target.prototype = new fn();
  //repair constructor
  target.prototype.constructor = target;
  //create instance
  ret = new target(arg);
  //add function to set source intance members
  ret.extend_init=function(arg){
    source.apply(this,arguments);
  };
  //set source intance members
  ret.extend_init(arg);
  //remove init
  delete ret.extend_init;
  return ret;
};
var Parent = function(arg){
  this.name=(arg && arg.name)? arg.name:undefined;
  this.age=(arg && arg.age)?arg.age:undefined;
};
Parent.prototype.whoAreYou = function(){
  return "I am "+this.name+" and I'm "+this.age+
    " years old.";
};
var Child = function(){
};

var t = extend(Parent,Child,{
  name: "t",
  age: 22});

console.log(t instanceof Child);//<=true
console.log(t instanceof Parent);//<=true
console.log(t.whoAreYou());//<=I am t and I'm 22 years old.

This causes some overhead so if you're creating a lot of these instances in a loop is best to set prototype before the loop, create and init instances in the loop and clean up after:

var extend = function(source,target){
  var fn=function(){},orgProto=target.prototype,
  thing;
  fn.prototype = source.prototype;
  //overwriting Child.prototype, usually you define inheritance
  //  first and add Child.prototype members after but when setting
  //  inheritance dynamic (not having Parent.apply(this,arguments in
  //  Childs body) the Child's prototype get overwritten
  target.prototype = new fn();
  //adding the Child.prototype members
  for(thing in orgProto){
    if(orgProto.hasOwnProperty(thing)){
      target.prototype[thing]=orgProto[thing];
    }
  }
  target.prototype.constructor = target;
  target.prototype.extend_init=function(){
    source.apply(this,arguments);
    return this;
  };
  return target;
};
var Parent = function(arg){
  this.name=(arg && arg.name)? arg.name:undefined;
  this.age=(arg && arg.age)?arg.age:undefined;
};
Parent.prototype.whoAreYou = function(){
  return "I am "+this.name+" and I'm "+this.age+
    " years old.";
};
var Child = function(){
};
Child.prototype.something=22;
//namesAndAges could be JSON data containing
// hundreds or even thousands of items
namesAndAges = [
  {name:"1",age:1},
  {name:"2",age:2},
  {name:"3",age:3},
  {name:"4",age:4},
  {name:"5",age:5}
  //, and many many more
];
var constr=extend(Parent,Child);
var persons=[];
for(var i = 0,len=namesAndAges.length;i<len;i++){
  //Child may have constructor parameters so we pass the parameter
  // object to both Child and Parent
  persons.push(new constr(namesAndAges[i])
    .extend_init(namesAndAges[i]));
};
delete constr.prototype.extend_init;
console.log(persons);

More on prototype, inheritance, overriding, calling super, mix ins and value of this here: https://stackoverflow.com/a/16063711/1641941

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • I agree with you regarding the naming conventions, in my case it's just an example, so in real project I will use names as you stated. Regarding the solution you suggest, I believe it works, but it looks very complicated to me and it gives as same result as what I'm wrote...or I miss something? – Alex Dn Nov 05 '13 at 08:06
  • @AlexDn In your code the constructor returned does not pass constructor parameters, it doesn't have child prototype and this.constructor for instances will not point to Child or Parent but to Class. – HMR Nov 05 '13 at 08:15
  • I made a little change: Constructor.prototype.constructor = fn;, now I able to pass parameters (not straight forward, but possible) and looks like instance constructor points to correct one (see Edit 2). Anyway I will look on your solution more closely, and possible I will really will find some benefits that with my solution I don't have. Thank you. – Alex Dn Nov 05 '13 at 08:39