Tuesday, March 19, 2024

Create an Object-oriented JavaScript Class Constructor

In JavaScript, the object constructor is the function that acts as the class template for instantiation using the new keyword (e.g., new MyClass()). The Emulate Classical Inheritance in JavaScript article described how to construct a Class definition in the classical Object-oriented (OO) style. This article will introduce the Class() code that will create our classes, pass on inherited Class members to subclasses, and return the class constructor. In doing so, we will learn about the “JScript DontEnum” bug and method borrowing.

The Class Generator Function

Not to be confused with the JavaScript Object constructor property, our Class() function can be considered to be a Class constructor in that it literally constructs a class from the passed object argument. In a nutshell, the function creates an inner object called “klass”. It has one method called initialize(), which acts like a typical OO constructor method: you pass it arguments and it assigns those values to its properties. The for loop iterates through the passed methods object and adds them to the klass’s prototype so that they will be shared by every instance of the class. Finally, a default constructor is created if there are none defined within the methods object (just like in Java). The klass constructor function is returned to the caller:

var Class = function(methods) {   
    var klass = function() {    
        this.initialize.apply(this, arguments);          
    };  
    
    for (var property in methods) { 
       klass.prototype[property] = methods[property];
    }
          
    if (!klass.prototype.initialize) klass.prototype.initialize = function(){};      
    
    return klass;    
};

Our class defines a two argument initialize constructor as well as an overridden toString(), inherited from the base Object. After creating a new instance, we can access its properties:

var Person = Class({ 
    initialize: function(name, age) {
        this.name = name;
        this.age  = age;
    },
    toString: function() {
        return "My name is "+this.name+" and I am "+this.age+" years old.";
    }
}); 

var alice = new Person('Alice', 26);
alert(alice.name); //displays "Alice"
alert(alice.age); //displays "26"
alert(alice.toString()); //displays "My name is Alice and I am 26 years old" in most browsers.
//IE 8 and below display the Object's toString() instead! "[Object object]"

Overriding Native Object Methods

The call to toString() in our above example does not work across all browsers. In particular, IE 8 and below will not iterate over the toString() or any other native Object methods in the for in loop! It turns out that this is a known issue called the “JScript DontEnum” bug. Internet Explorer’s ECMAScript support document explains the DontEnum issue as follows:

Note that JScript 5.x under Internet Explorer < 9 defines properties (see [ECMA-262] section 6.6.2.2) such that their DontEnum attribute is inherited from prototype properties with the same name. As a result of this, any properties that have the same name as built-in properties of a prototype object that have the DontEnum attribute are not included in an enumeration. However JScript 5.x under Internet Explorer 9 includes the properties that have the same name as built-in properties of a prototype object in an enumeration.

The ECMAScript 3 spec does recognize native objects’ internal DontEnum attribute as the determinant of what is not to be enumerated by a for…in loop. (If you’re curious, the read-only DontEnum attribute can be read by using Object.prototype.propertyIsEnumerable().) However, IE version 8 and below skips over any property in an object where there is a identically-named property in the object’s prototype chain that has the DontEnum attribute.

The DontEnum flag only affects for loops, so you can still access “hidden” overridden Object methods directly using the usual object[property] or object.property style accessors. Mozilla’s Development site suggests using Arrays instead of objects if you plan on iterating through them. An alternative that I particularly like is to store the Object native methods in an Array and loop through those. I whittled it down to seven methods that you could conceivably override in IE 8:

  var objMethods = [
     'constructor'
    ,'toString'
    ,'valueOf'
    ,'toLocaleString'
    ,'isPrototypeOf'
    ,'propertyIsEnumerable'
    ,'hasOwnProperty'
  ];

The next challenge is determining if one of our methods is overriding a native one. An interesting thing about native functions is that their toString() method hides the function body by displaying “[native code]”. Conversely, any function that contains runnable JS code is one of ours. That notion led me to conceive the following workaround:

  for(var i=0; i<objMethods.length; i++) {
    if (typeof source[objMethods[i]] === 'function'
       && source[objMethods[i]].toString().indexOf('[native code]') == -1 ) {
         //this line copies (borrows) the method from the source object
         destination[objMethods[i]] = source[objMethods[i]];
    }
  }
}

The first part of the if statement test that checks the source member type is probably excessive since all the objMethods’ contents are known to be existing methods anyway. Its real purpose is the verify that there is in fact a member of that name in the source object before performing the real test, which looks for the “[native code]” string in the function body. A more general check would have been to simply test for a non-null object member as in:

if (source[objMethods[i]] !== undefined 

//or even
if (source[objMethods[i]]

My personal view is that if you want a method, then look for a method!

In addition to fixing the DontEnum bug, IE 9 also implements Object.getOwnPropertyNames() to identify native methods. It returns an array much like the objMethods one above. Since the new function goes with the DontEnum bug fix, we can use it to determine whether or not to run the workaround code as follows:

if (!Object.getOwnPropertyNames) {
  var objMethods = [
  ...
}

Now the “alice” instance variable’s toString() method will return “My name is Alice and I am 26 years old.” like it should.

Conclusion

In the next article, we’ll be implementing the code to extend the parent class in the classical inheritance style.

Robert Gravelle
Robert Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured