How To Write JavaScript Classes
Posted by robert | Filed under JavaScript
Example files for this article.
Today I want to show you how I think JavaScript Classes should be structured. I’ve written classes many different ways over the years (Prototypal Inheritance, Object Literal Notation, Mootools, Base2js, Sencha Touch, Backbone and TypeScript). The following is a condensed example of the current format I use to structure my classes in JavaScript.
var BaseView = (function () { function BaseView() { } BaseView.prototype.createChildren = function () { }; BaseView.prototype.layoutChildren = function () { }; return BaseView; })();
You may have seen prototypal inheritance before but in the example above you can see it is wrapped with an extra closure. Sometimes this is referred to as an immediately invoked function expression (iife). The extra iife allows us to have access to the class when we start extending other classes. It is in this example to keep our class structure consistent.
Inheritance
Prototypal or class inheritance is where one class can inherit characteristics from another class. The benefit is you don’t have the same exact code or functionality in several different classes. If there is a bug in your code you don’t have to fix it in 10 different places. You just have to fix it in one class and all classes extending that class will be fixed.
Below is an example of how to extend the class above.
var AnotherView = (function () { var _super = Extend(AnotherView, BaseView); function AnotherView() { _super.call(this); } AnotherView.prototype.createChildren = function () { _super.prototype.createChildren.call(this); }; AnotherView.prototype.layoutChildren = function () { }; return AnotherView; })();
The format is constant with the the BaseView above but now we have this `_super` thing in our class. What `_super` allows you to do is call the functions/methods on the class you extended.
If you look at Extend in `var _super = Extend(AnotherView, BaseView);` it is a helper function that allows you to extend other classes easily. It will take the first class (AnotherView) and extend it with the second class (BaseView).
Below is the 12 line Extend function/class. Go ahead, put it in your projects to easily extend your classes.
var Extend = function (inheritorClass, baseClass) { for (var p in baseClass) { if (baseClass.hasOwnProperty(p)) { inheritorClass[p] = baseClass[p]; } } function __() { this.constructor = inheritorClass; } __.prototype = baseClass.prototype; inheritorClass.prototype = new __(); return baseClass; };
Real World Example
You may have a view class like below that you always use and repeat the same methods and logic throughout most of your classes. Lets use this class as our base for all of our other view classes. After, scroll down to the next sample class to see how we can extend this class.
var BaseView = (function () { function BaseView($element) { if (($element instanceof jQuery) === true && $element.length === 0) { // If a jQuery object was passed in and it doesn't have any items then exit. return; } this.$element = $element; this.isEnabled = false; this.init(); } BaseView.prototype.init = function() { // Kick off the view lifecycle this.createChildren() .setupHandlers() .enable() .layoutChildren(); return this; }; BaseView.prototype.createChildren = function() { // Used to create any objects for thr view and/or find references to child elements. return this; }; BaseView.prototype.setupHandlers = function() { // Creates a class reference to an event handler method that is bound to the scope of the object. return this; }; BaseView.prototype.layoutChildren = function() { // Optional use to set any child objects for the view. Also can be called later to update objects. return this; }; BaseView.prototype.enable = function() { if (this.isEnabled === true) { return this; } this.isEnabled = true; // Add event listeners for this class. return this; }; BaseView.prototype.disable = function() { if (!this.isEnabled === false) { return this; } this.isEnabled = false; // Remove event listeners for this class. return this; }; BaseView.prototype.destroy = function() { this.disable(); // Nulls out all properties of the class to prevent memory leak. for (var key in this) { if (this.hasOwnProperty(key)) { this[key] = null; } } }; return BaseView; })();
In the new class below you will notice we don’t need to set any properties or have any logic that we usually have in all of our view classes. Since we extended BaseView and called super throughout the different methods it will be handle behind the scenes and we can concentrate on what this new class needs to do.
var AnotherView = (function () { var _super = Extend(AnotherView, BaseView); function AnotherView($element) { // Put any properties above super. _super.call(this, $element); // Put any method calls below super. } AnotherView.prototype.createChildren = function() { _super.prototype.createChildren.call(this); return this; }; AnotherView.prototype.setupHandlers = function() { _super.prototype.setupHandlers.call(this); return this; }; AnotherView.prototype.layoutChildren = function() { return this; }; AnotherView.prototype.enable = function() { if (this.isEnabled) { return this; } return _super.prototype.enable.call(this); }; AnotherView.prototype.disable = function() { if (!this.isEnabled) { return this; } return _super.prototype.disable.call(this); }; AnotherView.prototype.destroy = function() { _super.prototype.destroy.call(this); }; return AnotherView; })();
I did not come up with this structure. This is what the TypeScript compiler spits out. I’ve just clean it up for easier use in native JavaScript projects.
Check out the example files for this article.
Less Typing with Templates and Snippets
Want to speed up your development? Check out my other article Templates/Snippets in your IDE to learn more.
Below is an example of a template I created that will create new classes that extends the BaseView above.