0

I recently switched to Object.create() instead of new for experimenting sake. How can I achieve multi inheritance, like classA -> classA's parent -> classA's parent's parent and so on?

example:

var test = Object.create(null); 
test.prototype = {
    greet: function () {
        console.info('hello world');
    },

    name: 'myName'
};

var test2 = Object.create(test.prototype);
test2.prototype = {
    name: 'newName'
};

var test3 = Object.create(test2.prototype);
test3.prototype = {
    name: 'another Name'
};

while test2 is still able to greet, test3 is obviously not because we used the prototype of test2 which doesn't have information about test and therefore no greet.

I read a few articles and it is highly discouraged to use __proto__ for inheritance. What is a the correct javascripty way of doing this?

Something like the following but with Object.create

test2.prototype = new test();
test2.constructor = test2;

test3.prototype = new test2();
test3.constructor = test3;

var a = new test3();
a.greet();
patchrail
  • 2,007
  • 1
  • 23
  • 33
  • 2
    see http://stackoverflow.com/questions/2709612/using-object-create-instead-of-new – mccainz Sep 30 '13 at 15:29
  • 1
    Technically speaking is test not an object because you inherit from null. Object in JavaScript are expected to have hasOwnProperty but test doesn't. If other people use your code they may expect test to be a JS object. Maybe create it like this: `test = Object.create(Object.prototype);` – HMR Oct 01 '13 at 03:36

2 Answers2

1

With Object.create objects inherit directly one from another, the prototype property has no role in it. In the first example i wrote things in the form closest to what you were doing before, but you don't need to set the properties of the object when you call Object.create. You can set them after the call without any issues (see second example).

var test1 = Object.create(null, {
    greet : {value : function() {
        console.info('hello world');
    }},

    name : {value : 'myName'}
});

var test2 = Object.create(test1, {
    name : {value : 'alteredProperty'}});

var test3 = Object.create(test2);

test3.greet(); // hello world
console.log(test3.name); // alteredProperty

Simpler example (with no property descriptors):

var test1 = Object.create(null);
test1.greet = function() {
    console.info('hello world');
};
test1.name = 'myName';

var test2 = Object.create(test1);
test2.name = 'alteredProperty';

var test3 = Object.create(test2);

test3.greet();
console.log(test3.name);

As HMR pointed out, each time you make a test1 object, you will create a new greet function and that is undesirable. The next example solves this problem by offloading methods to a prototype-like object.

// proto object
var Test1 = {
  greet : function() { console.info('hello world ' + this.name); },
  name : 'Test1'
};

// instance of Test1
var test1 = Object.create(Test1);

// proto object inheriting from Test1
var Test2 = Object.create(Test1)
Test2.name = 'Test2';

// instance of Test2
var test2 = Object.create(Test2);

// proto object inheriting from Test2
var Test3 = Object.create(Test2);
Test3.size = 'big';


// instance of Test3
var test3 = Object.create(Test3);

test3.greet(); // hello world Test2
console.info(test3.name); // Test2
console.info(test3.size); // big
test3.name = 'Mike';
test3.greet(); // hello world Mike

As you can see, the example is very much the same as the one above, but the difference is in how you treat some objects. Some of the objects (the one with Capital letter) act similarly to Constructors with prototypes: they are usually not used directly and they hold methods and default values for the built objects. This is purely conventional, as instances of a "Class" and inheriting "Classes" have exactly the same syntax. It is up to you to enforce that proto objects don't get misused.

Bonus:

function isInstanceOf(child, parent) {
  return Object.prototype.isPrototypeOf.call(parent, child);
}

console.info(isInstanceOf(test3, Test3)); // true
console.info(isInstanceOf(test3, Test1)); // true
console.info(isInstanceOf(test2, Test3)); // false
Tibos
  • 27,507
  • 4
  • 50
  • 64
  • 1
    Downvotes with no reason stated are useless. Please provide a reason, so i can learn from my mistake and not do it again. – Tibos Sep 30 '13 at 15:40
  • 1
    Not that I donwvoted but your code creates the functions every time an instance is created. you're not using prototype. I personally don't like it because it uses extra cpu and memory with no good reason but `not liking the new keyword` I like a lot of what `Douglas Crockford` has to say about JS but I think he recently has re considered not using `new` in favour of prototype. If you have the old JS the good parts book it still has some stuff in there he would not agree with today. – HMR Oct 01 '13 at 04:18
  • @HMR Thanks for the advice. The answer was focused on "what you can do" not on "how you should do it". I will add a recomendation that should solve the "function for each instance" problem. – Tibos Oct 01 '13 at 04:30
  • I'm sorry, I was the one who was wrong. Object.create seems to create a prototype, puts all the members of the parameter to Object.create on that prototype and returns an object (with no instance members). I don't have Chrome right now but that should show you instance and __proto__ values. If you see my revised answer I would be curious what it shows when `this` is logged to the console. – HMR Oct 01 '13 at 05:28
0

Using Object.create the way Tibos does looks like it'll put all the members of the passed in object to the prototype of the returned object.

// in firefox firebug running
// an empty page
var Definition = {
  name : 'Test1'
};
//doesn't matter where it's defined
Definition.greet=function() { 
  console.log(this);//<-what is this in Chrome?
};
Definition.arr=[];
// instance of Test1
var test1 = Object.create(Definition);
var test2 = Object.create(Definition);
console.log(test1.greet===test2.greet);//true
delete test2.greet
delete test2.greet
delete test2.greet
delete test2.greet//can't delete it
test2.greet();
console.log(test1.greet===test2.greet);//true
console.log(test1.arr===test2.arr);//true
test1.arr.push(1);
console.log(test2.arr);//=[1]
var things=[];
for(thing in test1){
  things.push(thing);
}
console.log("all things in test1:",things);
things=[];
for(thing in test1){
  if(test1.hasOwnProperty(thing)){
    things.push(thing);
  }
}
console.log("instance things in test1:",things);//nothing, no instance variables

[update]

As we see from the above code the Object.create returns an object that has all the members of the first parameter on it's prototype and the second parameter as it's instance members. (answer was there a long time from mccainz in the comments) Added benefit (ignoring IE8 and browsers that haven't been updated for years) is that you can specify enumerable, writable and configurable on instance members and you can create getters and setters that work like an assignment (instance.someprop=22 can actually be instance.someprop(22)).

To specify instance specific members there are several patterns you can use. Arguments are that when using these patterns your code looks just as "ugly" or even worse than using the new keyword but this would be a personal preference and would not take away the benefit from creating getters and setters or having extra control (enumerable, writable and configurable).

One pattern would be using the init function:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB).init("Bob");

A more complicated one that takes advantage of extra control is this:

var Person={
  talk:function(){console.log("I'm "+this.name);}
  //,other prototype stuff related to Person
};
var userCreator={
   processInstanceMembers:function(o,initObj){
     this.createName(o,initObj.name);
     Object.defineProperty(o,"_name",{writable:true});
     o.name=initObj.name;
   },
   get:function(initObj,inheritFrom){
     var ret=Object.create(inheritFrom||Person);
     this.processInstanceMembers(ret,initObj);
     return ret;
   },
   createName:function(o){//minimalise closure scope
     Object.defineProperty(o,"name",{
       get:function(){
         return this._name;
       },
       set:function(val){
         if(val.replace(/\s*/gm,"")===""){
           throw new Error("Name can't be empty, or only whitespaces");
         }
         this._name=val;
       },
       enumerable : true
     });
   }
};

//when creating an instance you can choose what to inherit from
//leave it out to inherit from Person
var u=userCreator.get({name:"Ben"});
u.talk();
u.name="Benji";
u.talk();
u.name="  ";//error, name can't be empty

Creating a new instance of Parent to set inheritance of Child isn't needed, you CAN use Object.create for this or helper functions:

var Child =function(){
  //get Parent's INSTANCE members defined in the 
  //parent function body with this.parentInstance=...
  Parent.apply(this,arguments);
}
Child.prototype=Object.create(Parent.prototype);
Child.prototype.constructor=Child;
Child.prototype.otherFn=function(){};

You may find this interesting, it has a helper function so you don't need to use Object.create if you don't want to.

Community
  • 1
  • 1
HMR
  • 37,593
  • 24
  • 91
  • 160
  • `this` is the object the method was called on (unless it was bound). In your example `Definition.greet()` => this===Definition, `test2.greet()` => this===test2 – Tibos Oct 01 '13 at 05:35
  • @Tibos What I mean is that Chrome shows instance and proto inherited members seperately. As my code shows there are no instance members and that could be problematic. Look at the `arr` example. Pushing a value in test1 instance changes `arr` member in test2. – HMR Oct 01 '13 at 05:52
  • @Tibos Yes, Chrome shows an empty object with all the members in `__proto__` – HMR Oct 01 '13 at 05:53