Extending Javascript prototypes

Extending Javascript prototypes

Browsers have varying support for different features in javascript, some support more functions, others support less. I'm not going to be so crass as to name names here but if we wanted to use Array.prototype.find we'd have problems on a certain browser from Redmond[1].

Thanks to the extensible nature of javascript we can simply implement the method ourselves.

Array.prototype.find = function(predicate) {
    // Our function here
};

This has the problem of overriding the original find function if it exists. If our implementation has a bug we now have replaced good and well-known functionality with it. We can avoid this by wrapping it in an if-check so we only add it if it''s needed

if (!Array.prototype.find) {
    Array.prototype.find = function(predicate) {
        // Our function here
    };
}

This looks good until we start getting some strange behaviour when we enumerate through an array

var myArray = [1......100];

for (var element in myArray)
{
    if (typeof element === "function")
    {
        // This point will be reached when element == Array.prototype.find
    }
}

Extending the Array prototype means that every array we create will have the find function as a property. This will be enumerated by default.

This is why a for .. in statement in javascript should generally be written like

for (var element in myArray) {
    if (myArray.hasOwnProperty(element)) {
        // This ensures the properties that explicitly belong to the array
    }
}

Ideally to extend an object we would define the property using the inbuilt Object.defineProperty method available to us like below

if (!Array.prototype.find) {

    Object.defineProperty(Array.prototype, "find", {
        enumerable: false,
        value: function(predicate) {
            "use strict";
            if (this == null) {
                throw new TypeError("Array.prototype.find called on null or undefined");
            }
            if (typeof predicate !== "function") {
                throw new TypeError("predicate must be a function");
            }
            var list = Object(this);
            var length = list.length >>> 0;
            var thisArg = arguments[1];
            var value;

            for (var i = 0; i < length; i++) {
                value = list[i];
                if (predicate.call(thisArg, value, i, list)) {
                    return value;
                }
            }
            return undefined;
        }
    });
}

Note the line enumerable: false
This ensures that it is not enumerated when using a for .. in loop.

Using this method of extending an object we can ensure that

  • We only define the property if it doesn't already exist by checked if (!Array.prototype.find)
  • The property will not be enumerated when cycling through an array

  1. Yes, it's Internet Explorer. It always is ↩︎