Rethinking JavaScript Objects

Share this article

Any developer that has worked with JavaScript for any amount of time is familiar with the arduous task of creating custom JavaScript objects. Coming from a background in Java, I’ve spent many hours trying to figure out how to make JavaScript objects act more like Java objects in every way. There just seems to be so much repetition in the way we need to define JavaScript objects, and I find that frustrating. After some thought and a lot of reading, I came up with a few ways to help eliminate some of the repetition that has plagued my coding for years.

Rethinking Inheritance

JavaScript, as many of us know, uses a prototype-based method of object inheritance. This states that if I have a “class” called ClassA, ClassB can inherit from it if I make ClassB’s prototype into an instance of ClassA, like so:

function ClassA () { 
}

function ClassB() {
}

ClassB.prototype = new ClassA;

This method has been augmented by the “object masquerading” technique used to copy an object’s properties (but not its methods) to another object. In order to fully inherit everything from ClassA into ClassB, we actually need to do this:

function ClassA () { 
}

function ClassB() {
       this.superclass = ClassA;
       this.superclass();
       delete this.superclass;
}

ClassB.prototype = new ClassA;

That’s an awful lot of code to accomplish something that is handled in Java like this:

Class ClassA { 
}

Class ClassB extends ClassA {
}

Going over this again and again in my development, I found myself growing increasingly annoyed at the repetitive and wasteful nature of all that JavaScript code I’d had to write just to inherit properties and methods from one class to another. Then I came across an interesting concept on Netscape’s DevEdge site.

Bob Clary, a Netscape Evangelist, wrote a function so simple I wondered why I hadn’t thought of it myself. In his article, “inheritFrom – A Simple Method of Inheritance Upon Demand”, he defines a simple method called inheritFrom() that can be used to copy all the properties and methods of an object to another object.

Essentially the same as every clone method every written in JavaScript, this function uses a for..in loop to iterate through the properties and methods of a given object, and copy them to another. A call would be made like this:

inheritFrom(ClassB, ClassA);

While that’s a good idea, it just didn’t fit into my coding style. I still consider JavaScript constructors as classes, and I write them thinking more of defining a Java class than a JavaScript constructor. It occurred to me that if I extended the native JavaScript Object class to include a method that did the same thing, all objects would automatically get this method, and I could essentially write something that looked and felt very similar to Java logic. My solution: the extends() method.

Just like Clary’s solution, the extends() method works on the basis that all properties and methods can be iterated through using bracket notation such as this:

object["Property"];  //Same as object.Property

The method itself looks like this:

Object.prototype.extends = function (oSuper) { 
       for (sProperty in oSuper) {
               this[sProperty] = oSuper[sProperty];
       }
}

This method accepts one parameter, oSuper, which is an instantiation of the class that we’d like to inherit from (or “extend”). The inner code is essentially the same as Clary’s inhertFrom() function, with the exceptions that:

  • I’m using the this keyword to indicate the object that is receiving the copied attributes, and
  • I removed the try..catch block so that that the method could also be used in Netscape Navigator 4.x.

With this method defined, we can now do the following:

function ClassA () {  
}  
 
function ClassB() {  
       this.extends(new ClassA());  
}

It is important to note that this method should be called first in your class (constructor) definition. Any additions should be made after this initial call. Also note that you must instantiate an object of the class you want to inherit from; you cannot simply pass in the class name itself. For example, this is incorrect:

function ClassA () {  
}  
 
function ClassB() {  
       this.extends(ClassA);   //INCORRECT!!!!  
}

This powerful new function also opens up the possibility of inheriting from two different classes and retaining the union of all properties and methods from the two. Let’s say ClassZ wants to inherit from both ClassY and ClassX. In this case, our code would look like this:

function ClassX (sMsg) {  
   this.message = sMsg;  
}  
 
function ClassY (sName) {  
   this.name = sName  
}  
 
function ClassZ() {  
       this.extends(new ClassX("Hello World"));  
       this.extends(new ClassY("Nicholas C. Zakas"));  
}  
 
var oTest = new ClassZ();  
alert(oTest.message);  
alert(oTest.name);

This method has greatly decreased the amount of time I’ve spent debugging inheritance problems because, instead of having four lines of code to get right, there’s now only one.

Rethinking Properties

In Java, we don’t often allow people direct access to properties. For instance, rarely do you see something like this:

Class ClassA {  
     public string message;  
}  
 
ClassA Test = new ClassA();  
Test.message = "Hello world";

Instead, classes are typically defined with getters and setters for each attribute (which is itself private), such as this:

Class ClassA {  
     private string message;  
 
     public void setMessage(String msg) {  
         this.message = msg;  
     }  
 
     public String getMessage() {  
         return this.message;  
     }  
}  
 
ClassA Test = new ClassA();  
Test.setMessage("Hello world");

This is a much better way to handle the properties of an object because of the extra measure of control it provides over the data. Yet in JavaScript, we often see this:

function ClassA() {  
   this.message = "";  
}  
 
var Test = new ClassA();  
Test.message = "Hello world";

In an effort to make my JavaScript classes more Java-like, I came to the conclusion that this process could be made simpler if I could just define a property and create a getter and setter automatically.

After some thought, I came up with the addProperty() method for the native JavaScript Object:

Object.prototype.addProperty = function (sName, vValue) {   
         
       this[sName] = vValue;  
         
       var sFuncName = sName.charAt(0).toUpperCase() + sName.substring(1, sName.length);  
         
       this["get" + sFuncName] = function () { return this[sName] };  
       this["set" + sFuncName] = function (vNewValue) {  
                       this[sName] = vNewValue;  
       };  
}

This method takes two parameters: sName is the name of the parameter, and vValue is its initial value. The first thing the method does is assign the property to the object, and gives it the initial value of vValue. Next, I create sFunc name to use as part of the getter and setter methods… this simply capitalizes the first letter in the property name so that it will look appropriate next to “get” and “set” (i.e. if the property name is “message”, the methods should be “getMessage” and “setMessage”). The next lines create the getter and setter methods for this object.

This can be used like so:

function ClassA () {   
   this.addProperty("message", "Hello world");  
}  
 
var Test = new ClassA();  
alert(Test.getMessage());    //outputs "Hello world"  
Test.setMessage("Goodbye world");  
alert(Test.getMessage());    //outputs "Goodbye world"

Defining properties like this is much easier, but I realized that I might need to get some information from these methods at some point. After all, what if a “set” is not allowed? I could just override the method, but I’d have to do that for every property.

Instead, I opted to create some code that mimics the onpropertychange event of IE. That is, an onpropertychange() method would be defined, and whenever any property of the object changed, this method would be called with an object that described the event. My custom event object, though, has only a few properties:

  • propertyName – the name of the property that has been changed
  • propertyOldValue – the old value of the property
  • propertyNewValue – the new value of the property
  • returnValue – true by default, can be set to false in the onpropertychange() method to nullify the change

The code now looks like this:

Object.prototype.addProperty = function (sName, vValue) {   
         
       this[sName] = vValue;  
         
       var sFuncName = sName.charAt(0).toUpperCase() + sName.substring(1, sName.length);  
         
       this["get" + sFuncName] = function () { return this[sName] };  
       this["set" + sFuncName] = function (vNewValue) {  
               var vOldValue = this["get" + sFuncName]();  
               var oEvent = {    
                       propertyName: sName,    
                       propertyOldValue: vOldValue,    
                       propertyNewValue: vNewValue,    
                       returnValue: true    
                       };  
               this.onpropertychange(oEvent);  
               if (oEvent.returnValue) {  
                       this[sName] = oEvent.propertyNewValue;  
               }  
 
       };  
}  
 
//default onpropertychange() method – does nothing  
Object.prototype.onpropertychange = function (oEvent) {  
       
}

As you can see, only the setter method has been changed. The first thing it does now is get the old value of the property by calling the corresponding getter method. Next, the custom event object is created. Each of the four properties are initialized, and then object is passed to the onpropertychange() method.

By default, the onpropertychange() method does nothing. It is intended to be overridden when new classes are defined. If the custom event object returns from the onpropertychange() method with returnValue still set to true, then the property is updated. If not, the property is not updated, effectively making it a read-only property.

With this code in place, we can now do the following:

function ClassB() {    
   this.addProperty("message", "Hello world");    
   this.addProperty("name", "Nicholas C. Zakas");    
}    
   
ClassB.prototype.onpropertychange = function(oEvent) {    
   if (oEvent.propertyName == "name") {    
       oEvent.returnValue = false;  //don't allow name to be changed    
   }    
}    
   
var Test = new ClassB();    
alert(Test.getMessage()); //outputs "Hello world"    
Test.setMessage("Goodbye world");    
alert(Test.getMessage());  //outputs "Goodbye world"    
alert(Test.getName());      //outputs "Nicholas C. Zakas"    
Test.setName("Michael A. Smith");    
alert(Test.getName());       //outputs "Nicholas C. Zakas"

A slight addition can be made in order to allow detection of the type of value being stored in a property. In effect, we are adding type checking to any properties added with the addProperty() method:

Object.prototype.addProperty = function (sType, sName, vValue) {    
           
       if (typeof vValue != sType) {    
           alert("Property " + sName + " must be of type " + sType + ".");    
           return;    
       }    
         
       this[sName] = vValue;    
           
       var sFuncName = sName.charAt(0).toUpperCase() + sName.substring(1, sName.length);    
           
       this["get" + sFuncName] = function () { return this[sName] };    
       this["set" + sFuncName] = function (vNewValue) {    
   
                if (typeof vNewValue != sType) {    
                   alert("Property " + sName + " must be of type " + sType + ".");    
                   return;    
               }    
   
               var vOldValue = this["get" + sFuncName]();    
               var oEvent = {    
                       propertyName: sName,    
                       propertyOldValue: vOldValue,    
                       propertyNewValue: vNewValue,    
                       returnValue: true    
                       };    
               this.onpropertychange(oEvent);    
               if (oEvent.returnValue) {    
                       this[sName] = oEvent.propertyNewValue;    
               }    
   
       };    
}

Here, I added a single parameter, sType, which defines the type of data the property holds. I made it the first parameter because, again, this is similar to Java. I also added two checks using the JavaScript typeof operator: one on the initial value assignment, another when the property is changed (in reality, these should throw errors, but for compatibility with Netscape 4.x, I opted for alerts). For those who are unaware, the typeof operator returns one of the following values:

  • “undefined” – the value doesn’t exist.
  • “string”
  • “number”
  • “function”
  • “object”
  • “boolean”

The parameter sType must match one of these values in order for this to be a valid check. In most cases, this should be good enough (if not, you can always write your own function to use instead of typeof). It’s important to note that a value of null will return “object” from the typeof operator.

Updating the previous example, we can now do this:

function ClassB() {    
   this.addProperty("string", "message", "Hello world");    
   this.addProperty("string", "name", "Nicholas C. Zakas");    
   this.addProperty("number", "age", 25);    
}    
   
ClassB.prototype.onpropertychange = function(oEvent) {    
   if (oEvent.propertyName == "name") {    
       oEvent.returnValue = false;  //don't allow name to be changed    
   }    
}    
   
var Test = new ClassB();    
alert(Test.getMessage()); //outputs "Hello world"    
Test.setMessage("Goodbye world");    
alert(Test.getMessage());  //outputs "Goodbye world"    
alert(Test.getName());      //outputs "Nicholas C. Zakas"    
Test.setName("Michael A. Smith");    
alert(Test.getName());       //outputs "Nicholas C. Zakas"    
alert(Test.getAge());      //outputs 25    
Test.setAge("45");         //generates error message    
alert(Test.getName());       //outputs 25
Conclusion

While JavaScript has left us with some severe limitations in the creation of custom objects and classes, it is also flexible enough to find solutions. JavaScript seems to be heading in a direction that brings it closer to Java in syntax and implementation (see the (JavaScript 2.0 proposal), but in the meantime the code presented in this article should make your development efforts a little less painful.

I have tested the code presented in this article on Netscape Navigator 4.79, Internet Explorer 6.0 and Netscape 7.0 (Mozilla 1.0.1), but I believe it should work in most modern browsers.

Nicholas C. ZakasNicholas C. Zakas
View Author

Nicholas is a front-end engineer, author, and speaker. He currently works at Box making the web application awesome. Prior to that, he worked at Yahoo! for almost five years, where he was front-end tech lead for the Yahoo! homepage and a contributor to the YUI library

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week