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.
October 6th, 2010 on 1:20 pm
I’m really happy that I found your series. Thanks a lot.
November 5th, 2010 on 2:26 pm
(I’d just like to prefix my comment with this disclaimer: I’m not critising your work in any way, these are a very informative and helpful series of tutorials, but I feel slightly disapointed at what must be a limitation of javascript, it’s just that the loop _Feels Wrong_)
In a previous screencase you mention that we shouldn’t loop to copy the methods due to the time to copy the methods of large classes and the fact that the addition of new methods to the prototype are not then available in the child.
In this example you loop to copy the methods onto the new constructor. Isn’t there any sort of “OR” in a similar vain the the bitwise operator that would allow both sets of methods to be created without the loop, i.e.,
Isn’t there a way to replace:
for (var p in init)
cstr.prototype[p] = init[p];
with something similar to:
cstr.prototype[p] = cstr.prototype[p] | init[p];
November 5th, 2010 on 6:45 pm
Hi Ian,
In general, yes you should avoid looping whenever possible as it does tend to slow down code. And JavaScript, being an interpreted language, is already fairly slow (though newer JavaScript engines are addressing that). I don’t recall in which screencast I talked about adding functions by looping, but I think it was in the context of copying all the methods of a class. In this case, we are only copying the methods that are being added to the descendant class. I’d love to come up with a way to do it without looping.
In your code example, the OR operator, in the way you’ve used it, will assign whichever of the two values is not null or undefined. That means that it won’t override a function that already exists in cstr.prototype. In other words, if cstr.prototype[p] is not null or undefined then it will be assigned to cstr.prototype[p]. It will only assign init[p] if it does not already exist in cstr.prototype.
Oh, and by the way, feel free to (respectfully) criticize my work. That’s how we all learn.
November 10th, 2010 on 3:12 pm
Hi Jason,
It has always been my feeling that programmers should be forced to use slow machines as this (nearly) always forces us to write better, more efficient code. Hiding where the browser is doing extra, unnecessary work doesn’t help in the long run.
Yes, I realise my example doesn’t work, otherwise I’d have posted a working, loop-free version
I did try quite a few things (even bitwise XOR) to see if you can trick the JS engine into performing the merge without the loop, to no avail.
I have created a function that performs some of your requirements, the removal of the two lines that assign the prototype and the base, whilst it also removes the need for the extra global inheriting object and the check for if (arguments[0] === inheriting) return;.
Both of which also felt wrong to me.
I’m a professional programmer, but not in JavaScript so would appreciate any insight in to what I’ve probably done wrong in my implementation of a createClass function. As such, rather than try to paste code here (which never works well in comments) if I may have your permission to republish your examples modified to use my function to my website and then I’ll post the link back here.
Ian.
November 11th, 2010 on 10:58 pm
Go for it, Ian. All I ask is that you place a link from your site to the original source code here. That way the conversation doesn’t get broken.