JavaScript Tutorial for Programmers - Prototype(2)

I still remember the days of debugging CORS problem when I put together some projects using JavaScript (& ajax), “a very particular programming language” in my first impression. Recently I got a great opportunity. The new role uses JS, the browser-side script that is now winning in all sides, as the major language. So I tooke it as a good chance to learn JS more systematically, and this series will be part of the outcome of my study. As the name implies, I will not cover primary level such as “if, else” (condition), “for” (or any kinds of loops), or basic OOP concepts. Instead, I will focus only on differences so you can learn this versatile language like reviewing a pull request, and use it the next day in your next awesome project.

Prototype chain based inheritance

Class inheritance can be implemented using a technique called prototype chain. So an object can be traced back to its ancestor(s) using a prototype instance that is embedded (i.e., __proto__) in it, like a gene. For the end purpose of inheritance, i.e., code reusing, prototype chain also enables the accessing of members that only exist in ancestors, hence whenever such a member (method or property) is accessed, the runtime will check upwards the prototype chain that contains members information of the whole inheritance (sub-)tree.

Simple, but not easy

Phew, it’s hard to explain coding concept without code. So next I am going to implement this prototype chain step by step. Moreover, I make it a trial and error process in hope that the read can simulate a real developing experience, and which I hope can be remembered (by you) easier.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ASuperClass = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};

var ASubClass = function() {
};

ASubClass.prototype = ASuperClass.prototype; //let's make the chain

var subobj = new ASubClass();

alert(subobj instanceof ASuperClass);
alert(subobj.aproperty);
alert(subobj.amethod());

Result:

1
2
true
undefined

Uncaught TypeError: subobj.amethod is not a function

Though the type can be recognized by the runtime, the properties can not be accessed as expected. So I think we need to call the super class constructor to initialize the missing properties. Let’s give it a go.

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  var ASuperClass = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};

var ASubClass = function() {
++ ASuperClass();
};

ASubClass.prototype = ASuperClass.prototype;
++SubClass.prototype.constructor = ASubClass;

var subobj = new ASubClass();
alert(subobj instanceof ASuperClass);
alert(subobj.aproperty);
alert(subobj.amethod());

Unfortunately, the result is the same. Hmmm, this time is the corrupted this in the superclass constructor. If this is not your first reaction, please ⬅ to my previous post that is dedicated to this topic.

In order to fix this, we can either add:

1
2
var tmpf = ASuperClass.bind(this);
tmpf();

or:

1
ASuperClass.apply(this);

, as per discussed in the post. I will use the second method because, well, it’s one line less:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  var ASuperClass = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};

var ASubClass = function() {
++ ASuperClass.apply(this);
};

ASubClass.prototype = ASuperClass.prototype;
ASubClass.prototype.constructor = ASubClass;

var subobj = new ASubClass();
alert(subobj instanceof ASuperClass);
alert(subobj.aproperty);
alert(subobj.amethod());

Result:

1
2
3
true
a
b

Finally we nailed it! Are we? No:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  var ASuperClass = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};

var ASubClass = function() {
ASuperClass.apply(this);
};

ASubClass.prototype = ASuperClass.prototype;
ASubClass.prototype.constructor = ASubClass;

++ASubClass.prototype.another_property = 'c';

var subobj = new ASubClass();
++var superobj = new ASuperClass();
++alert(subobj.another_property);
++alert(superobj.another_property);

Result:

1
2
c
c

In fact, we just made something else but inheritance, which does not exist in real world. I tentatively call it coupling. Though we made a wrong decision from the very first step, still the other effort we made can be largely reused. I’ve heard versions of stories about one guy, who failed a kickstart project, one day realized the pain eventually paid off in some other way. This is just like that. So let’s keep up.

Prototype DEcoupling

In common (and correct) practice, prototype chain is established using two entities, an intermediate class (which is also an object, first class) and an intermediate object (,normal) in order to decouple the “thing” we made just now, and the designated object relation is given below:

1
2
3
4
Subclass.prototype
|---intermediate object
|---.__proto__
|---IntermediateClass.prototype === Superclass.prototype

in which, the intermediate object is an instance of the intermediate class.

Let’s get back to the snippet I showed in the beginning of my last post:

1
2
3
4
5
6
7
function inherits(ChildClass, ParentClass) {
function IntermediateClass() {}
IntermediateClass.prototype = ParentClass.prototype;
ChildClass.prototype = new IntermediateClass;
ChildClass.prototype.constructor = ChildClass;
return ChildClass;
};

I hope the above text has made the above function clear now. Then let’s verify the function in code:

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
  var ASuperClass = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};

var ASubClass = function() {
ASuperClass.apply(this);
};

--ASubClass.prototype = ASuperClass.prototype;
++inherits(ASubClass, ASuperClass);

ASubClass.prototype.another_property = 'c';

var subobj = new ASubClass();
var superobj = new ASuperClass();

alert(subobj instanceof ASuperClass);
alert(subobj instanceof ASubClass);
alert(subobj.aproperty);
alert(subobj.amethod());
alert(subobj.another_property);

alert(superobj instanceof ASuperClass);
alert(superobj instanceof ASubClass);
alert(superobj.aproperty);
alert(superobj.amethod());
alert(superobj.another_property);

A better version of the inherits() can be implemented using Object.create() that can simplify the creation of the intermediate entities.

1
2
3
4
5
6
7
8
  function inherits(ChildClass, ParentClass) {
-- function IntermediateClass() {}
-- IntermediateClass.prototype = ParentClass.prototype;
-- ChildClass.prototype = new IntermediateClass;
++ ChildClass.prototype = Object.create(ParentClass.prototype);
ChildClass.prototype.constructor = ChildClass;
return ChildClass;
};

To be honest, I am feeling a bit exciting when checking the result:)

1
2
3
4
5
6
7
8
9
10
11
true
true
a
b
c
// this line is artificial
true
false
a
b
undefined

Allllll good!

Wait, there is one last piece, what is a

prototype.constructor

You may have noticed, I stealthily added the constructor related code from here and omit the explanation. I did it on purpose in order to make the logic flow smooth. Now let’s scrutinize it:

1
2
3
class <----------------|
|------.prototype.constructor
|------......(I have drawn this)

prototype.constructor is a special method referring to the class itself. Normally the link is made by the runtime,

Example:

1
2
3
4
5
6
var ASuperClass = function() {
this.aproperty = 'a';
this.amethod = function () {return 'b';};
};

alert(ASuperClass.prototype.constructor === ASuperClass);

Result:

1
true

However, the original ChildClass.prototype.constructor is broken when we chained up the prototypes in

1
function inherits(ChildClass, ParentClass)

So we need to assign it back to ChildClass:

1
ChildClass.prototype.constructor = ChildClass;