Look at my previous post for the first part - the introduction about OO in JavaScript.
Inheritance
Just a small digression if you didn't come across this. In JavaScript, there is a 'call' method on any function. For example:
function f(a, b) {
alert([this.hi, a, b]);
}
var passAsThis = { hi: 'Hello!' }
f.call(passAsThis, 55, 66);
What the above will output is Hello!,55,66. The f.call(passAsThis, 55, 66) call is doing something interesting. It calls the given function (in this example - 'f') passing the first argument ('passAsThis') to it as 'this' and all other arguments (55, 66) normally. This means that inside the function, 'this' refers to whatever you passed, which is convenient to do simulate parent class method calling. If you have a method on parent class you want to call instead of the overridden method in the child class, you could do this:
function ParentClass(number) {
this.number = number;
}
ParentClass.prototype.displayNumber = function() {
alert(['ParentClass', this.number]);
}
function ChildClass(number) {
this.number = number * number;
}
ChildClass.prototype = new ParentClass();
// @Override (just joking - but in Java you could)
ChildClass.prototype.displayNumber = function() {
alert('ChildClass');
ParentClass.prototype.displayNumber.call(this);
}
var child = new ChildClass(5);
child.displayNumber();
This will output: ChildClass and ParentClass,25. It's a little clumsy, but the call: ParentClass.prototype.displayNumber.call(this) is what in Java you would write as super.displayNumber(). A lot of characters thrown away. This can be a hard thing when you have to call parent class a lot.
Inheritance is an important OO concept. JavaScript doesn't support this very well. Some propose the following solution:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.display = function() {
alert('My name is ' + this.name + ' and I am ' + this.age);
}
function Teacher(name, age, subject) {
Person.call(this, name, age);
this.subject = subject;
}
Teacher.prototype = new Person();
Teacher.prototype.teach = function() {
alert('Teaching ' + this.subject);
}
This "almost" works in the sense that you get the right hierarchy. For example, you could now do:
var teacher = new Teacher('Foo Bar', 35, 'math');
teacher.display();
teacher.teach();
This means that you inherited the 'display' method from Person class and you could add a new method 'teach'. This is basics of OO inheritance, so we are OK. Take a look at this for a very good explanation of this method. This method is very acceptable if you don't mess to much into OO.
However, the call: Teacher.prototype = new Person() is really a function call, so all your code in Person gets executed. This is OK if you don't have any side effects, but if your class was WebServiceClient and you would connect to Web service in the constructor, this would not be a good solution at all. First, every time you attempt to inherit from WebServiceClient, you would try to execute the code in the constructor - really not what you intended. Second, when you execute it, you don't get any parameters, so you could possibly go even worse. Not a good situation at all.
One more problem is global state modified in constructors. Assume you have a PersonsCache, which would hold all the Person objects ever created. You could not put adding the just-made instance in the constructor of Person, like this:
function Person(name, age) {
this.name = name;
this.age = age;
PersonsCache.addPerson(this);
}
Every time you do the inheritance Teacher.prototype = new Person(), you would call the constructor and add something. That something is a dangling reference to your new instance object. You will normally never use it, but what is more important - you will have more persons then you really have...
Similar to this, if you ever touch the DOM or any HTML-related stuff, this will break. The problem is that you inherit while the script is loading, which is before body of the document is created. You can circumvent this by e.g. putting everything inside body onLoad event handler, but again - that is just clumsy boilerplate and distracts you from what you should be doing.
Another problem, common to all "solutions" (both the one above and all of the mentioned below) is that method overriding is not supported very well. Assume you have three classes: B, C and D. Assume C inherits from B, D inherits from C. Assume all of the classes have one method called 'sayHi'. Now, you could override the method only in class D, for example. To call the base methods from that method, you need to supply the actual base class. This may seem strange - you always know your base classes! Not true, though. Say I now change the hierarchy by inheriting B from class A, so we have A -> B -> C -> D. If the method 'sayHi' stays in B, that is OK. If it, however, gets moved to class A for some reason, you would have to find all calls like B.sayHi.call(this, ...) and change them to A.sayHi.call(this, ...). In Java, it would still be super.sayHi(...). Maybe I am nitpicking here - if you override in both C and D, even in Java you couldn't do it without casting (i.e. ((A)this).sayHi() would call sayHi from class A). Here is Java code that is an example of this:
class A {
public void sayHi() {
System.out.println("A");
}
}
class B extends A {
public void sayHi() {
System.out.println("B");
}
}
class C extends B {
public void sayHi() {
System.out.println("C");
}
}
class D extends C {
public void sayHi() {
super.sayHi();
System.out.println("D");
}
}
public class Inherit {
public static void main(String[] args) {
new D().sayHi();
}
}
Try commenting out different sayHi methods in base classes and see how it behaves. In Java, super.sayHi() finds the "first method up the hierarchy".
There are ways to circumvent some of JavaScript's inheritance "features". Discussion below is about inheritance without calling the base constructors during inheritance.
Use empty constructors and construct using special constructor method
Back to empty-constructor method. This could look like this for the above Person & Teacher example:
function Person() {}
Person.ctor = function(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.display = function() {
alert('My name is ' + this.name + ' and I am ' + this.age);
}
function Teacher() {}
Teacher.prototype = new Person();
Teacher.ctor = function(name, age, subject) {
Person.ctor.call(this, name, age);
this.subject = subject;
}
Teacher.prototype.teach = function() {
alert('Teaching ' + this.subject);
}
var teacher = new Teacher();
// #1
Teacher.ctor.call(teacher, 'Foo Bar', 35, 'math');
teacher.display();
teacher.teach();
It obviously works pretty well, since we have removed the unwanted call when doing the inheritance. It also obviously looks very clumsy and there are much more characters to type with no good reason.
This is not very OO in another sense. The construction process is separated. If you would try to call teacher.display() in place of #1 comment, you actually could and you would get incorrect results, of course.
Copy prototype
Some recommend doing a straight prototype-copy. I used this one on two of my projects and it worked very well.
The idea here is very simple. It gets answered when you ask - what means "to inherit" in JavaScript? Take a look at the above ways to implement inheritance. What all they do is take the parent class' prototype on the start. This is what happens when you say something like Teacher.prototype = new Person(). Actually, this does something else, but the end result is the same - Teacher.prototype becomes the reference to parts that parent class' prototype contains. The keyword is "copy". JavaScript is dynamic, so we can verify this:
function Parent() {
}
Parent.prototype.display = function() {
alert('Parent.display');
}
Parent.prototype.callDisplay = function() {
this.display();
}
function Child() {
}
Child.prototype = new Parent();
// Change parent's definition of display.
Parent.prototype.display = function() {
alert('Parent.display - modified')
}
var parent = new Parent();
parent.callDisplay();
var child = new Child();
child.callDisplay();
As you can see, Child's prototype now contains a definition of 'display' inherited from Parent and it changes as it should. How is this a copy then? Again, see this for a very good explanation. How this really works is like this. When you say new Child(), you actually get the object which has __proto__ key. Take a look:
var s = '';
var childProto = new Child().__proto__;
for(var i in childProto) {
s += i + ': ' + childProto[i] + '\n';
}
alert(s)
You see the display method. That is child's display method? No, it's Parent's. How JavaScript uses this thing is rather interesting - it walks it as a tree. Here:
var childProto = new Child().__proto__;
var parentProto = childProto.__proto__;
alert(childProto == Parent.prototype)
Say you have var child = new Child(). This child has it's __proto__ pointing to Child.prototype. Each next reference to __proto__ gets you one level upper the inheritance chain, thus child.__proto__.__proto__ gives you Parent.prototype. If we had more levels, we would have got GrandParent.prototype and so on.
The same thing, however, happens when you pick a variable or method. Say you want to do child.display(). JavaScript first looks in child - do you have a display? Nope. OK, go one level up - look at child.__proto__. Child doesn't define display, sorry. OK, how about child.__proto__.__proto__? Now, since child.__proto__.__proto__ == Parent.prototype, you have a display here. So it gets called. You can think of this as a sum of all things up the inheritance chain, but child classes have precedence - if they override something, it is overridden as they want. You can even override something on an object-level:
var s = '';
var child1 = new Child();
var child2 = new Child();
child2.display = function() {
alert('child2 - modified');
}
child1.display();
child2.display();
You will get 'Parent.display - modified' and 'child2 - modified'.
We need a little more help from here. If you take a look, it says what var child = new Child() does:
- Makes new object (i.e. new Object()) - let's call it 'cobj'
Calls Child constructor, passing the created 'cobj' object as this
Implicitly sets cobj.__proto__ to Child.prototype
Sets child to cobj
- Makes new object (i.e. new Object()) - let's call it 'cobj'
Calls Child constructor, passing the created 'cobj' object as this
Implicitly sets cobj.__proto__ to Child.prototype
Remember child.__proto__ == cobj.__proto__ == Child.prototype, but also Child.prototype.__proto__ == Parent.prototype.
Thus, child.__proto__.__proto__ == Parent.prototype and this is how it all can work nicely
If you have all the above assumptions in place and you are OK with them, then you can simulate the above mechanism. The only thing you actually need to do is copy the things from Parent.prototype to Child.prototype and that is it. You lose __proto__ hierarchy, true, but you can simulate that, too (although not completely). It works, however, pretty nice. Here is how I did it on my last project:
Klass = {
extend: function(klassChild, klassParent) {
var pkChild = klassChild.prototype;
var pkParent = klassParent.prototype;
for(var i in pkParent)
pkChild[i] = pkParent[i];
pkChild.constructor = klassChild;
pkChild.parent = klassParent;
pkChild.klassName = Klass.name(klassChild);
},
create: function(klass) {
var prototype = klass.prototype;
prototype.constructor = klass;
prototype.klassName = Klass.name(klass);
},
name: function(klass) {
return klass.toString().match(/function (.*)\(/)[1];
}
}
The Klass is the utility object which can be used like this:
function Parent() {
}
Klass.create(Parent);
Parent.prototype.display = function() {
alert('Parent.display: ' + this.value);
}
Parent.prototype.callDisplay = function() {
this.display();
}
Parent.prototype.value = 1;
function Child() {
}
Klass.extend(Child, Parent);
Child.prototype.value = 2;
var child = new Child();
child.callDisplay();
Here, instanceof doesn't work, class changes do not reflect to child classes. These are the imperfections.
Check arguments for inheritance-related info
Another method is to stop inheritance calls in their roots. For example:
var ONGOING_INHERITANCE = {};
function Person(name, age) {
if(arguments[0] == ONGOING_INHERITANCE) return;
this.name = name;
this.age = age;
}
Person.prototype.display = function() {
alert('My name is ' + this.name + ' and I am ' + this.age);
}
function Teacher(name, age, subject) {
if(arguments[0] == ONGOING_INHERITANCE) return;
Person.call(this, name, age);
this.subject = subject;
}
Teacher.prototype = new Person(ONGOING_INHERITANCE);
Teacher.prototype.teach = function() {
alert('Teaching ' + this.subject);
}
var teacher = new Teacher('Foo Bar', 35, 'math');
teacher.display();
teacher.teach();
alert(teacher instanceof Person);
This code works as expected. It is lot less clumsy then the previous solutions. The last line tells you that it gives the right results, i.e. teacher really is the instance of Person class.
For the alternative approach to this, take a look at this page.
This is most of what should be considered basic JavaScript OO-like programming. Hope you enjoyed it.
No comments:
Post a Comment