Yet an other article about javacript inheritance
« Wednesday, June 26, 2013 »

javascript

Javascript is a prototyped language. Unlike other languages, there is no class but only prototypes. Any object in javascript should be useable as a Prototype. It's not exactly clear yet but I'll try to explain a bit more. In javascript, we can do something that I'd call Object chaining. We can create objects from other objects. The child object has the generator object as prototype. People might have told you about functions having a prototype but it is much more than that because the objects themselves are prototypes. Let me show you an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Animal prototype
var Animal = {
   talk: function () {
      console.log('...');   
   }
};

// Dog prototype
var Dog = Object.create(Animal);
Dog.talk = function () {
    console.log('Woof');
};

var SmallDog = Object.create(Dog);
SmallDog.talk = function() {
    console.log('grrrr waf');
}


var animal = Object.create(Animal);
var dog = Object.create(Dog);
var small = Object.create(SmallDog);

animal.talk()
> ...

dog.talk()
> Woof

small.talk()
> grrrr waf

The following code would work if we were to create an object using the new operator. For our case, we use pure prototype inheritance without using any function. We could probably mock the instanceof for objects that aren't created with new but with Object.create. This is beyond the subject of the article. Take my word for granted and it should be a valid asumption. I'll add a complete example at the end.

1
2
3
4
5
6
small instanceof Animal
> true
dog instanceof Dog
> true
small instanceof Dog
> true

Here we have the most basic sample of inheritance in Javascript. Each object inherit the prototype chain of the prototype they were created from. It's not yet clear but I'll try to show an example. People could tend to believe that it's just creating a new object with the properties of the object before.

Lets edit the example above and add at the end.

1
2
3
4
5
6
7
8
9
SmallDog.talk = undefined // Defining a property of the object to undefined
small.talk() // Will raise an exception because undefined isn't callable as expected

SmallDog.talk = null // Defining a property of the object to null
small.talk() // Will raise an exception because null isn't callable as expected

delete SmallDog.talk // undefine the property, it's gone. Not undefined not null. It's really gone
small.talk() // will not raise an exception
> Woof

As you might have understood, when affecting a variable to SmallDog, it will create a new property in the prototype chain. If a property is defined, the property lookup ends. Setting a value to undefined or to null is a way to hide properties to its descendant. One might think that setting null or undefined could be a way to create private variables. But it's not.

1
2
3
4
5
6
SmallDog.talk = undefined;
Animal.what = function () {
    this.talk();
};

small.what() // will raise an exception because talk is not a function

Since the object that is sent to the function what in Animal is a SmallDog that can't talk. Then the property lookup will find that the property talk in SmallDog is undefined and it will stop there. Deleting the key in SmallDog will return the function in Dog. Setting a new function will return the function in SmallDog. Setting a new function talk to small will also make the small.what() call work.

When doing lookup for properties, it will check for each parent prototypes until it there is no more parent object. When no parent object is found, it should return undefined. And now one last thing.

The non standard __proto__

As I said, it's not a standard extension of ecmascript. I believe most browsers supports it but it's not sure that we can trust it. Taking our example above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
dog.__proto__ == Dog
dov.__proto__.__proto__ == Animal
SmallDog.__proto__ == Dog
Animal.__proto__ == Object

// Thus
small.__proto__ == SmallDog
> true
small.__proto__.__proto__ == Dog
> true
small.__proto__.__proto__.__proto__ == Animal
> true
small.__proto__.__proto__.__proto__.__proto__ == Object
> true
small.__proto__.__proto__.__proto__.__proto__.__proto__ == null
> true

Then what about instanceof

Well now, it should be pretty clear that whenever you check for instanceof. It's only checking if the second parameter is part of the prototype chain.

Now an example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var Animal = function () {
    this.alive = true;
};
Animal.prototype = {
    alive: false, // Default to false
    lives: true,
    dies: function () {
        this.alive = false;
    }
};
var Mammals = function () {
    Animal.call(this);
};
// Defining the prototype
Mammals.prototype = Object.create(Animal);
Mammals.prototype.isMammal = true;
Mammals.prototype.talk = function () {
    raise "Undefined method";
};

var Human = function (name) {
    Mammals.call(this);
    this.name = name;
};
Human.prototype = Object.create(Mammals);
Human.prototype.talk = function () {
    console.log("I'm a human");
}

var Dog = function (name, color) {
    Mammals.call(this);
    this.name = name;
    this.color = color;
};
Dog.prototype = Object.create(Mammals);
Dog.talk = function () {
    console.log("I'm a human");
};

Here each prototype can be used with a constructor. The constructor exists to set predefined properties to new objects. Each prototype is well defined with their parent prototypes.

Note that in each constructor, I'm calling the parent object constructor. It is optional and could be useless. Also note that my Mammals constructor is empty and simply call the Animal constructor. The Animal constructor only defines alive to true. Other variables are left alone in the prototype. If the prototype changes, every object will see the changes, except that if the values are defined as properties of any other children prototype, only the descendant will get affected.

comments powered by Disqus

Copyright © 2015 Loïc Faure-Lacroix