Make your hand-drawings digital vector (video)
Make your hand-drawings digital vector (video)
Before reading this post, I recommend you to read my previous post “Interfacing in JavaScript” first.
Class inheritance is used to define a family of classes. This is perfectly straightforward in any object-oriented language, except for JavaScript. But it can be done.
Let’s say you have a class called Animal and you want to extend this class to form a class Dog. First, we define the classes:
Animal = ( function() {
var C = function Animal( name ) {
this.name = name;
};
merge( C.prototype, {
getName: function() {
return this.name;
}
} );
return C;
} )();
Dog = ( function() {
var C = function Dog() {
};
merge( C.prototype, {
talk: function() {
return 'bark';
}
} );
return C;
} )();
So what’s next? How do we ‘couple’ the two classes and in such a way that Dog is a sub class of Animal?
We can do that by making our sub class prototype an instance of the base class. The following snippet illustrates this approach.
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
This ensures that any instance of Dog will also be an instance of Animal.
var dogInstance = new Dog();
typeof dogInstance; // == 'object'
dogInstance instanceof Dog; // == true
dogInstance instanceof Animal; // == true
In many situations, you want to have access to the base class and in particular the base methods you override. Just add a reference to the base class when extending.
Dog.base = Animal.prototype;
Dog.base.constructor = Animal;
This way, we can access the base class.
Dog = ( function() {
var C = function Dog( name ) {
C.base.constructor.call( this, name );
};
return C;
} )();
var dogInstance = new Dog( 'Barney' );
dogInstance.getName(); // == 'Barney'
To sum things up, we can create a function for extending classes such as the merge function we’ve defined in the previous part of this series.
// Makes subClass an instance of baseClass
// and applies the members defined in overrides
// to subClass
function extend(
subClass, baseClass, overrides
) {
function F() {};
F.prototype = baseClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.base = baseClass.prototype;
subClass.base.constructor = baseClass;
if ( overrides ) {
merge( subClass.prototype, overrides );
}
};
First, I’ve defined a function called F. This F is a temporary class that is a copy of the baseClass. That’s why the F’s prototype is set to baseClass’ prototype. Then subClass’ prototype is set to an instance of F (which is actually the baseClass). Then we define the subClass’ constructor to the original sub class. And at last we define the base property for accessing the base class’ members from within the sub class.
Now you can inherit classes like this:
Animal = ( function() {
var C = function( name ) {
this.name = name;
};
merge( C.prototype, {
getName: function() {
return this.name;
}
} );
return C;
} )();
Dog = ( function() {
var C = function( name ) {
C.base.constructor.call( this, name );
};
extend( C, Animal, {
talk: function() {
return 'bark';
}
} );
return C;
} )();
Terrier = ( function() {
var C = function( name ) {
C.base.constructor.call( this, name );
};
extend( C, Dog, {
isTerrier: function() {
return true;
}
} );
return C;
} )();
var myDog = new Terrier( 'Barney' );
typeof myDog; // == 'object'
myDog instanceof Animal; // == true
myDog instanceof Dog; // == true
myDog instanceof Terrier; // == true
myDog.talk(); // == 'bark'
myDog.getName(); // == 'Barney'
myDog.isTerrier(); // == true
Well, that’s it about inheritance.
All code in this article is tested to work with Firefox 3.6, Safari 533, Chrome 12, IE 6, IE 7, IE 8, IE 9, Opera 9+.
For a better understanding of my view on how objects and classes work in JavaScript and how to define them, you should read my post “Defining classes in JavaScript” first. This article will continue on that basis.
The main purpose of defining a class is for reusability and for defining a common way to talk to an object: its interface.
As seen in my previous post in this series, in JavaScript everything is an object, but JavaScript does not expiclitly support classes. A class is essentially a function object that you can prototype by using the new keyword. The prototype of a function is the class its interface. The interface specifies the type of the object.
We’ve seen earlier class definitions like this:
function MyClass() {
};
MyClass.prototype = {
myFancyMethod: function() {
// do something here
}
};
This looks familiar, but it is wrong! The prototype property is completely overwritten by an object literal containing member properties and methods. This means that everything, including your prototype’s precious constructor, is overwritten. That disables typing of your objects and makes any instance of your class having the default Object constructor. Better is to augment the existing prototype of our MyClass function with its new members.
You can do this by creating a merge function:
// Applies properties from sender to reciever.
function merge( receiver, sender ) {
for ( var p in sender ) {
receiver[ p ] = sender[ p ];
}
};
This way, only items defined in sender are overwritten in receiver and so keeping the base interface. This enables typing of objects as it won’t overwrite your prototype’s constructor property.
Ofcourse, you can also declare each member with a separate expression, like this:
MyClass.prototype.mySpecialMethod = function() {};
But that requires a lot of touching the keyboard for larger classes. In the following snippet I illustrate the use of the merge function.
MyClass = ( function() {
var C = function MyClass() {
// constructor
};
merge( C.prototype, {
myFancyMethod: function() {
// method stub
}
} );
return C;
} )();
JavaScript supports three types of modifiers for class members: public, privileged and private.
Public and privileged members are accessible by other objects and private members are not. Only privileged and private members can access private members. By default, all prototype members are public. If you need to define privileged or private members, you should do so in the constructor.
MyClass = function() {
var C = function MyClass() {
var privateProperty = 'this is private';
var privateMethod = function() {
// ommit the 'this' keyword!
return privateProperty;
};
this.privilegedMethod = function() {
return privateProperty;
};
};
merge( C.prototype, {
publicProperty: 'this is public',
publicMethod: function() {
return this.publicProperty;
}
} );
return C;
} )();
So how does this work? It works because of closures. Everything defined in the context of the constructor function is kept and can be used later on in the private and privileged functions.
The difference between private and privileged members is that all private members are declared using the var keyword and all privileged members are declared within the object this. Private and privileged members must be declared in the constructor. If not, they will be just public.
Static class members are not declared within the prototype property, but directly on the class function.
MyClass = function() {
var C = function MyClass() {
};
C.STATIC_VALUE = 'this is static';
C.staticMethod = function(){};
return C;
} )();
// This can be accessed like this
MyClass.staticMethod();
All code in this article is tested to work with Firefox 3.6, Safari 533, Chrome 12, IE 6, IE 7, IE 8, IE 9, Opera 9+.
Next up, class inheritance.
JavaScript is fully object-oriented. In JavaScript, everything is an object. For example, you can write:
'literal string'.length == 14
Even though everything is an object, JavaScript does not care what kind of object it is. It just evaluates whether the property or method you are calling exists in the object it is called on at run-time.
If you want to have maintainable code (and you want that), it is usually best to define classes for your objects, so you can program against an interface or do inheritance and other object-oriented programming principles. In JavaScript this is possible, but it is not so straightforward because there is no standardized way to do this.
In the next sections I show you how to achieve class definitions that are both maintainable and extendable.
The most common way of defining a simple class in JavaScript is as follows:
function MyClass() {
};
var myClassInstance = new MyClass();
Specifying instance members this way:
MyClass.prototype.doSomething = function() {
return true;
};
myClassInstance.doSomething(); // returns true
Nothing new so far. This is the way we used to define classes and instance members. What you get is a flat object of type MyClass with a method called doSomething.
When we perform type checking on this class instance, we get the following results:
typeof myClassInstance; // == 'object'
myClassInstance instanceof MyClass; // == true
As you can see, JavaScript ‘knows’ that the myClassInstance variable is of type MyClass. It is the name of the function that we used as constructor.
This all is perfectly as we expect it to be. Now let’s go a step further and encapsulate this in a namespace.
Something you see more often these days, are self-executing functions.
( function() {
// some code here
} )();
The reason why we define those functions is because it scopes your variables. If you encapsulate your class within a self-executing function, you ensure that everything you declare in it keeps inside of it and it does not pollute the global namespace (the window object in most cases). Other advantages are that you can treat everything in it as a whole and you can define namespaces, like so:
MyNamespace = ( function() {
// you class code here
} )();
Now when defining a class we should encapsulate the definition into our namespace.
MyClass = ( function() {
var C = function MyClass() {
// put constructor code here
};
C.prototype.doSomething = function() {
return true;
};
return C;
} )();
What I’ve done here is encapsulating the class definition in a self-executing function that assigns the class definition to a namespace with name MyClass. MyClass is the class name. We can instantiate it like we used to:
var myClassInstance = new MyClass();
typeof myClassInstance; // == 'object'
myClassInstance instanceof MyClass; // == true
Let’s take a closer look at the definition code. The class definition is entirely encapsulated in its own namespace, that is, the anonymous, self-executing function. Within that function, I’ve declared a variable C (this C is the ‘c’ from the word class that I find convenient to work with).
This C is at first a function that is the constructor of our class. Then I assign a new property to its prototype member by the name doSomething. This is a public method. All properties assigned to the prototype property are used as instance members.
And finally, C is returned to the self-executing function. The outcome of the self-executing, anonymous function is assigned to MyClass which is defined in the global namespace.
It’s generally good practice of keeping layers separated. Which means in this case, your class definition cannot know what its name is at write-time (I use the term ‘write-time’ to indicate the difference between the written code and the way the parser interprets the code at run-time). So how do you reference the class itself within the class?
You can use the C variable to do this. If you consequently using the name C for you classes, you can never be wrong. It will always refer to the class itself, because of the encapsulation. Also, note that I mention your class definition to refer to C, not the class instance. A class instance can refer to itself with the this keyword. For example:
MyClass = function() {
var C = function MyClass() {
// == 12
var value = C.STATIC_VALUE;
// == 'another value'
var anotherValue = this.getValue();
};
C.STATIC_VALUE = 12;
C.prototype.value = 'another value';
C.prototype.getValue = function() {
return this.value;
};
return C;
} )();
Why is this safe? It is because of closures. JavaScript keeps the scope context of a function at its declaration in the definition. So when the function is called, the references within the scope of creation are kept and applied to the execution.
All code in this article is tested to work with Firefox 3.6, Safari 533, Chrome 12, IE 6, IE 7, IE 8, IE 9, Opera 9+.
Now you have some robust class definitions for JavaScript. This will make it easier to maintain and extend JavaScript classes.
Next up, how to robustly set up a prototype interface for classes with JavaScript.