Creating JavaScript Classes, Part 5: The Class Function (with screencast)
by Jason S. Kerchner on Mar 11, 2009 (filed in Development)
View Screencast | Download code
In the previous posts of this series, I looked at using JavaScript to create class hierarchies that would include property and method inheritance, and method overrides. Today, I’ve created a single JavaScript function that will automate this process for me. This is very similar in functionality to the YUI, ExtJS and other implementations that are out there, though this version is pretty bare-bones at this point. Which is good for learning the basics of how it all works.
Let’s first take a look at the original Employee class and see how I inherited from the Person class. The inline comments describe some of the more interesting points in the code, relating to inheritance, overrides and the like. Check out the earlier posts in this series to learn how these work.
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 | // The constructor function function Employee(first, last, company) { // Bail out if inheriting if (arguments[0] === inheriting) return; // Call base constructor Employee.base.constructor.call(this, first, last); this.company = company; }; // Inherit prototype methods of Person Employee.prototype = new Person(inheriting); // Allows access to original base class methods Employee.base = Person.prototype; // New method added to this class Employee.prototype.getWebSite = function() { return 'http://www.' + this.company + '.com'; }; // Overrides Person (and calls base class method) Employee.prototype.getFullName = function(firstLastFormat) { if (firstLastFormat) return Employee.base.getFullName.call(this); else return this.lastName + ', ' + this.firstName; }; |
Overall, this is a lot of code, and a lot to remember when creating classes and managing inheritance. Here is the same Employee class, using the new Class function.
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 | Employee = Class({ // Inherits from person inherits: Person, // This is the constructor construct: function(first, last, company) { // Calls base class constructor Employee.base.construct.call(this, first, last); this.company = company; }, // New method added to this class getWebSite: function() { return 'http://www.' + this.company + '.com'; }, // Overrides method in Person (and calls base class method) getFullName: function(firstLastFormat) { if (firstLastFormat) return Employee.base.getFullName.call(this); else return this.lastName + ', ' + this.firstName; } }); |
Now that’s a lot simpler to write, read and understand. Here is the function that makes the magic happen. I’ll be referring to this function a lot in the code explanations below.
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 inheriting = { }; function Class(init) { // Create function to be used as constructor that checks // if we are inheriting, then call real constructor var cstr = function() { if (arguments[0] === inheriting) return; this.construct.apply(this, arguments); }; // If we are inheriting, copy the prototype, // otherwise assign a new prototype if (init.inherits) { cstr.prototype = new init.inherits(inheriting); cstr.base = init.inherits.prototype; delete init.inherits; // Prevents adding to prototype later }; // Copy all properties of init to the prototype // (adds/overrides base class methods) for (var p in init) cstr.prototype[p] = init[p]; // Return the constructor function (this is the class) return cstr; }; |
As a side note, I rarely name a function as a noun unless it is intended to be used as a constructor function for creating objects, but in this case, I liked the look of the code to create a class. This, I think, is pretty clear as to what is happening.
Employee = Class({ /* ... */ });
Now, let’s break it down and look at how this Class function works.
Class is a function that takes a single argument. This argument is an initialization object, called init, and this argument describes the class that we will be creating.
Referring to the Class function code above, line 7 creates a new function called cstr (which stands for “constructor”). This function is the actual constructor for the new class that we are creating. This new function will ultimately be returned from the Class function, and it is the function that will be executed when creating a new instance of a class (in this case, Employee). Since this function is not actually executing yet, I’m not going to explain its contents just yet. We’ll get to that later.
On line 14, the Class function checks if the init argument has an inherits property. The init.inherits property is a reference to the class that this class inherits from (in this case, the Employee class inherits from the Person class). Lines 15 and 16 copy the prototype methods from the base class and create a reference to the original base class methods, respectively. These are equivalent to our previously manually coded lines (refer to the code at the top of this post for the original version of the Employee class):
1 2 3 4 5 6 7 8 9 | // This... cstr.prototype = new init.inherits(inheriting); // ...is equivalent to this... Employee.prototype = new Person(inheriting); // And this... cstr.base = init.inherits.prototype; // ...is equivalent to this... Employee.base = Person.prototype; |
After the inheritance is completed, I delete the init.inherits property on line 17. I’ll explain why I’m doing this in a moment.
Lines 22 and 23 copy all of the methods of the init argument to the prototype of our new constructor function. We need to copy them one by one so that we don’t replace the inherited prototype methods. In this example, we inherited from Person, so the entire Person.prototype was copied to this constructor’s prototype in line 15. If we just assigned cstr.prototype = init, then we would replace all of the inherited prototype methods.
Now, back to line 17 where I deleted the init.inherits property. Deleting it prevents it from being copied to the prototype in lines 22 and 23.
Finally, in line 26, we return our new constructor function. This is our new class.
Now, back to the cstr function definition on line 7. This is the function I’ve returned from the Class function, and it represents the new class. So when I create a new class instance, this is the function that gets executed. The first line of this function (line 8 in the Class function code above) checks to see if we are inheriting by checking if the first argument is the unique inheriting object. If it is, then we don’t need to execute the entire constructor code (this was covered in Creating JavaScript Classes, Part 3 of this series). It is placed here so that I don’t have to worry about doing it myself, like I did in the original Employee class on line 4 (see first code listing at the top of this post).
The next line, line 9, executes the construct function that was defined in our init argument. Remember, this function is executing while creating a new class instance, so the this variable is set to the new, empty object that we are creating. The arguments variable contains a reference to any arguments that were passed into the constructor function, and we use the JavaScript apply function to pass those arguments to the construct function. Incidentally, I use the word “construct” (instead of “constructor”) for two reasons. First, some browsers were giving me problems with the name “constructor”, and second, “construct” is a verb which is how I generally name all my functions.
As a side note, I want to point out that the Class function takes a single argument, in JSON format. This means that a class definition could be loaded via an AJAX request from the server, and passed into the Class function. This is an advanced topic, but it would allow streaming classes from the server “on-demand”.
OK, that’s enough for today. Please let me know what you think!
December 10th, 2009 on 10:42 am
Wow, good stuff. The only thing I don’t really understand about Javascript is this stuff.
Thanks
December 15th, 2009 on 11:50 am
Hey Kevin, glad you found the information helpful. JavaScript can be a tricky beast, but keep at it and you’ll get it.