jared@protoscript.net


javascript :: extending the language · 2005-04-21

Todd Ditchendorf, of ditchnet.org, shared a technique to extend javascript’s standard Array type. This technique highlights the expressive power that prototype-based languages provide to developers.

 Extensibility

Let us assume that we want to add a method, last, to our Array type. In most* class-based languages, one would be forced to subclass the Array class and add the last method to their new class (which is why you sometimes see classes in static languages with names like BetterArray or even ArrayWithLast). The intent of the programmer is to add functionality to the Array type, not to create a new, separate type that happens to provide the two sets of functionality.

In javascript, you can achieve this by adding a property to the prototype property of the type (or object) you are trying to extend:

Array.prototype.last = function()
{
     return this[this.length-1];
}

The first line adds the last method as a property of the prototype object of Array. Whenever javascript attempts to access a method meth (or any property, for that matter) of object Obj, the interpreter first checks to see if Obj.meth exists. If the method cannot be found, javascript searches the object’s prototype chain. This chain consists of all of the properties of the object’s prototype, as well as it’s prototype’s prototype, and so on (Fortunately, this chain is finite; Object’s prototype property is set to null). So, to check the prototype chain for the method, the interpreter looks for Obj.prototype.meth, then Obj.prototype.prototype.meth, and so on.**

From this point forward, we can access the {font-family:lucida console}last% method of any array we create. A slightly more subtle effect of this is that all arrays, even those created before this code was executed, can access this new method.

The benefits of using this technique are probably rather clear, but I’ll enumerate just a few of them anyways:

 An Example

I’d like to proceed to an example, specifically the contains method that Todd added to Array.

First we will need some sample arrays to use for testing purposes:

unlucky = [7,13,99];
num = 1;
nums = [new String(num),"2","3","4"];

And here is Todd’s implementation of contains:

Array.prototype.contains = function (element) {
    for (var i = 0; i < this.length; i++) {
            if (this[i]==element) {
                return true;
            }
    }        
            return false
};

Now, we can test the membership of an array for a specific element like this:

if(unlucky.contains(13)){document.write("13 is unlucky!")}

Let’s try a—very contrived—example with our other array:

if(nums.contains(new String(num))){document.write("We found "+num)}

If you run this code, you’ll find that, even though you are storing num in the array and passing that same variable to contains, the message never prints! This is due to the strange manner in which javascript handles the comparison of String objects, especially those allocated with the String constructor. I only mention this problem because it has brought me hours of suffering while working on a large project. My colleagues and I were in the habit of new String()-ing things to ensure that we were dealing with the string representation of objects before concatenating to them or comparing them. I’m not sure whether this problem occurs in any other context, but it’s a good idea to reduce the potentiality for error as much as we can.

I realized that the problem vanished if toString was called on either of the objects being compared. It seems like the interpreter is checking the equality of the identities of the objects when both are created with an explicit call to the constructor; otherwise, it tests the value of the objects. My first idea was to change the body of the for loop to this:

        if(typeof(element)=="string")
        {
            if (this[i].toString()==element) {
                return true;
            }
        }
        else
        {
            if (this[i]==element) {
                return true;
            }
        }

But when a string is allocated using the String constructor, passing it to the typeof method returns “object”. So, I decided to check for the String constructor directly:

       if (this[i]==element || element.constructor==String &&
                               this[i]==element.toString() ) {

Finally, we’re getting the results we expect! Fortunately, the factors that cause this odd behavior are only found rarely. It is a small price to pay for the dynamic power that javascript provides!


*Ruby is one exception. I’m still researching, but I often get the impression that, internally, Ruby is closer to a prototype-based language than to a traditional, static, class-based language. Of course, one could employ containment to accomplish a similar effect, but the solution is still an extra step away from the problem domain.

**This is accurate conceptually; under the hood, the details may differ slightly.

NB: I use the word “type” to refer to the concepts behind javascript’s constructor functions. Of course, these “types” are just regular objects with a few special responsibilities, but it’s helpful to be able to differentiate between an instance of Array and Array itself.

UPDATE: The ECMAScript specification states that the proper way to convert a String object to a string value is to call String as a function. Using this technique, the example would look like:

       if (this[i]==element || element.constructor==String &&
                               this[i]==String(element) ) {

* * *

Comment

  1. The equals operater (==) uses same object identity between two objects and same value between native value types. If you use new Number, new String or new Boolean you will get an object and not a native type and therefore the object must be the same for it to return true.

    Using new in front of String, Number and Boolean is more less useless. The only use I can see for them is if one wants to add methods and fields to the actual instance object.

    And by the way, why haven’t you told me you had a blog? I thought you were inactive online ;-)
    Erik Arvidsson    Aug 25, 1:34pm    #
name Remember
email
http://
Message
  <?>

© Jared Nuzzolillo