You don't have to add the method to every object -- only the kind of object which you are working with. If it is a string method, you can add it to String.prototype
and have it defined on all strings.
var obj = { "text" : "Hi" };
// We only want to add this method to strings.
String.prototype.addText = function(){
return this+' there!';
};
alert("OBJECT TEXT+method: " + obj.text.addText());
Note that this does make methods enumerable, which means they show up in a for..in
loop.
Object.defineProperty
If you can only care about supporting browsers since 2010 (no IE8), then I highly recommend using Object.defineProperty
to define properties so that they won't be enumerable:
var obj = { foo: 'bar' };
// Extending all objects this way is likely to break other scripts...
Object.prototype.methodA = function() { return 'A'; };
// Instead you can do extensions this way...
Object.defineProperty(Object.prototype, 'methodB', {
value: function() { return 'B'; },
// Prevent the method from showing up in for..in
enumerable: false,
// Allow overwriting this method.
writable: true,
// Allow reconfiguring this method.
configurable: true
});
for (var propertyName in obj) {
console.log(propertyName);
// Logs: "foo" and "methodA" but not "methodB"
}
The code above logs the "foo" property and the "methodA" property but it doesn't log the "methodB" property because we defined it as non-enumerable. You'll also notice built-in methods like "toString", "valueOf", "hasOwnProperty", etc also don't show up. This is because they are also defined as non-enumerable.
This way other scripts will be allowed to use for..in
freely and everything should work as expected.
Going back to specific built-ins
We can define non-enumerable methods on specific types of objects with Object.defineProperty
as well. For instance, the following adds a contains
method to all arrays which will return true
if the array contains a value and false
if it doesn't:
Object.defineProperty(Array.prototype, 'contains', {
value: (function() {
// We want to store the `indexOf` method so that we can call
// it as a function. This is called uncurrying `this`.
// It's useful for ensuring integrity, but I'm mainly using
// it here so that we can also call it on objects which aren't
// true Arrays.
var indexOf = Function.prototype.call.bind(Array.prototype.indexOf);
return function(value) {
if (this == null)
throw new TypeError('Cannot be called on null or undefined.');
return !!~indexOf(this, value);
}
})(),
enumerable: false,
writable: true,
configurable: true
});
var colors = [ 'red', 'green', 'blue', 'orange' ];
console.log(colors.contains('green')); // => true
console.log(colors.contains('purple')); // => false
Note that since we defined this method on Array.prototype
, it is only available on arrays. It is not available on other objects. However, in the spirit of other array methods, it is written with enough generality that it can be called on array-like objects:
function foo() {
Array.prototype.contains.call(arguments, 5);
}
console.log(foo(1, 2, 3, 4, 5)); // => true
console.log(foo(6, 7, 8, 9, 10)); // => false
Calling contains
on arguments
works above, event though arguments
isn't a true array.
Simplifying the Boilerplate
Using Object.defineProperty
provides a lot of power, but it also requires a lot of extra code which most people would rather not type all the time. That was understood when the ECMAScript committee defined the function, but they assumed people could write helper functions to make the code cleaner. Keep this in mind. For example, you can always do something like the following:
var define = (function() {
// Let's inherit from null so that we can protect against weird situations
// like Object.prototype.get = function() { };
// See: https://mail.mozilla.org/pipermail/es-discuss/2012-November/026705.html
var desc = Object.create(null);
desc.enumerable = false;
desc.writable = true;
desc.configurable = true;
return function define(constructor, name, value) {
if (typeof constructor != 'function'
|| !('prototype' in constructor))
throw new TypeError('Constructor expected.');
desc.value = value;
Object.defineProperty(constructor.prototype, name, desc);
}
})();
Then you can do:
define(String, 'addText', function() {
return this + ' there!';
});
console.log('Hi'.addText()); // => "Hi there!"
There are even some small libraries which have been developed to help with some of this stuff. Check out Andrea Giammarchi's redefine.js as an example.
Caution
The only real caution here is that if you add your own methods to built-ins, it's possible to have name clashes with (a) future versions of JavaScript or (b) other scripts. (The name-clash problem is being resolved in the next version of JavaScript with symbols.) Many would argue that this is a big enough problem that you shouldn't ever modify built-ins but should stick to modifying only the stuff you "own" -- things you created with your own constructors. I would tend to agree in some (or many) situations, but I think for your own personal use and learning, playing around with built-in prototypes can be a very helpful and fun thing to do.
Check out this article on weighing some of the costs of modifying built-ins for a more detailed description of possible drawbacks: http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/ Note that this article is a little dated (1 1/2 years) and one of his major complaints is the enumerability thing, which is possible to overcome with Object.defineProperty
in all modern browsers. As I said, the other major problem (name-clashes) will be solved in the next version of JavaScript. Things are getting better!