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
Yes, it's Internet Explorer. It always is ↩︎