JavaScript: What dangers are in extending Array.prototype?


Google JavaScript Style Guide advises against extending the Array.prototype. However, I used Array.prototype.filter = Array.prototype.filter || function(...) {...} as a way to have it (and similar methods) in browsers where they do not exist. MDN actually provides similar example.

I am aware about Object.prototype issues, but Array is not a hash table.

What issues may arise while extending Array.prototype that made Google advise against it?

Most people missed the point on this one. Polyfilling or shimming standard functionality like Array.prototype.filter so that it works in older browsers is a good idea in my opinion. Don't listen to the haters. Mozilla even shows you how to do this on the MDN. Usually the advice for not extending Array.prototype or other native prototypes might come down to one of these:

  1. for..in might not work properly
  2. Someone else might also want to extend Array with the same function name
  3. It might not work properly in every browser, even with the shim.

Here are my responses:

  1. You don't need to use for..in on Array's usually. If you do you can use hasOwnProperty to make sure it's legit.
  2. Only extend natives when you know you're the only one doing it OR when it's standard stuff like Array.prototype.filter.
  3. This is annoying and has bit me. Old IE sometimes has problems with adding this kind of functionality. You'll just have to see if it works in a case by case basis. For me the problem I had was adding Object.keys to IE7. It seemed to stop working under certain circumstances. Your mileage may vary.

Check out these references:

Good luck!


I'll give you the bullet points, with key sentences, from Nicholas Zakas' excellent article Maintainable JavaScript: Don’t modify objects you don’t own:

  • Dependability: "The simple explanation is that an enterprise software product needs a consistent and dependable execution environment to be maintainable."
  • Incompatible implementations: "Another peril of modifying objects that you don’t own is the possibility of naming collisions and incompatible implementations."
  • What if everyone did it?: "Simply put: if everyone on your team modified objects that they didn’t own, you’d quickly run into naming collisions, incompatible implementations, and maintenance nightmares."

Basically, don't do it. Even if your project is never going to be used by anyone else, and you're never going to import third party code, don't do it. You'll establish a horrible habit that could be hard to break when you start trying to play nice with others.


As a modern update to Jamund Ferguson's answer:

Usually the advice for not extending Array.prototype or other native prototypes might come down to one of these:

  1. for..in might not work properly
  2. Someone else might also want to extend Array with the same function name
  3. It might not work properly in every browser, even with the shim.

Points 1. and 2. can now be mitigated in ES6 by using a Symbol to add your method.

It makes for a slightly more clumsy call structure, but adds a property that isn't iterated over and can't be easily duplicated.

// Any string works but a namespace may make library code easier to debug. 
var myMethod = Symbol('MyNamespace::myMethod');

Array.prototype[ myMethod ] = function(){ /* ... */ };

var arr = [];

// slightly clumsier call syntax
arr[myMethod]();

// Also works for objects
Object.prototype[ myMethod ] = function(){ /* ... */ };

Pros:

  • For..in works as expected, symbols aren't iterated over.
  • No clash of method names as symbols are local to scope and take effort to retrieve.

Cons:


Extending Array.prototype in your own application code is safe (unless you use for .. in on arrays, in which case you need to pay for that and have fun refactoring them).

Extending native host objects in libraries you intend others to use is not cool. You have no right to corrupt the environment of other people in your own library.

Either do this behind an optional method like lib.extendNatives() or have [].filter as a requirement.

Extending Natives and Host Objects


Prototype does this. It's evil. The following snippet demonstrates how doing so can produce unexpected results:

<script language="javascript" src="https://ajax.googleapis.com/ajax/libs/prototype/1.7.0.0/prototype.js"></script>
<script language="javascript">
  a = ["not", "only", "four", "elements"];
  for (var i in a)
    document.writeln(a[i]);
</script>

The result:

not only four elements function each(iterator, context) { var index = 0; . . .

and about 5000 characters more.


Some people use for ... in loops to iterate through arrays. If you add a method to the prototype, the loop will also try to iterate over that key. Of course, you shouldn't use it for this, but some people do anyway.


You can easily create somekind of sandbox with poser library.

Take a look on https://github.com/bevacqua/poser

var Array2 = require('poser').Array();
// <- Array

Array2.prototype.eat = function () {
  var r = this[0];
  delete this[0];
  console.log('Y U NO .shift()?');
  return r;
};

var a = new Array2(3, 5, 7);

console.log(Object.keys(Array2.prototype), Object.keys(Array.prototype))

I believe this question deserves an updated ES6 answer.

ES5

First of all, as many people have already stated. Extending the native prototypes to shim or polyfill new standards or fix bugs is standard practice and not harmful. For example if a browser doesn't support the .filter method if (!Array.prototype.filter) you are free to add this functionality on your own. In-fact, the language is designed to do exactly this to manage backwards compatibility.

Now, you'd be forgving for thinking that since JavaScript object use prototypal inheritance, extending a native object like Array.prototype without interfering should be easy, but up until ES6 it's not been feasible.

Unlike objects for example, you had to rely and modifying the Array.prototype to add your own custom methods. As others have pointed out, this is bad because it pollutes the Global namespace, can interfere with other code in an unexpected way, has potential security issues, is a cardinal sin etc.

In ES5 you can try hacking this but the implementations aren't really practically useful. For more in depth information, I recommend you check out this very informative post: http://perfectionkills.com/how-ecmascript-5-still-does-not-allow-to-subclass-an-array/

You can add a method to an array, or even an array constructor but you run into issues trying to work with the native array methods that rely on the length property. Worst of all, these methods are going to return a native Array.prototype and not your shiny new sub-class array, ie: subClassArray.slice(0) instanceof subClassArray === false.

ES6

However, now with ES6 you can subclass builtins using class combined with extends Array that overcomes all these issues. It leaves the Array.prototype intact, creates a new sub-class and the array methods it inherits will be of the same sub-class! https://hacks.mozilla.org/2015/08/es6-in-depth-subclassing/

See the fiddle below for a demonstration: https://jsfiddle.net/dmq8o0q4/1/


Extending the prototype is a trick that only works once. You do and you use a library that also does it (in an incompatible way) and boom!


The function you are overriding could be used by the internal javascript calls and that could lead to unexpected results. Thats one of the reasons for the guideline

For example I overrode indexOf function of array and it messed up accessing array using [].


I want to add an additional answer that allows extending the Array prototype without breaking for .. in loops, and without requiring use of hasOwnPropery:

Don't use this bad approach which causes prototype values to appear in for .. in:

Array.prototype.foo = function() { return 'foo'; };
Array.prototype.bar = function() { return 'bar'; };

let a = [ 1, 2, 3, 4 ];
console.log(`Foo: ${a.foo()}`);
console.log(`Bar: ${a.bar()}`);
console.log('==== Enumerate: ====');
for (let v in a) console.log(v);

Instead use Object.defineProperty, with enumerable: false - it exists for pretty much exactly this reason!

Object.defineProperty(Array.prototype, 'foo', {
  value: function() { return 'foo'; },
  enumerable: false
});
Object.defineProperty(Array.prototype, 'bar', {
  value: function() { return 'bar'; },
  enumerable: false
});

let a = [ 1, 2, 3, 4 ];
console.log(`Foo: ${a.foo()}`);
console.log(`Bar: ${a.bar()}`);
console.log('==== Enumerate: ====');
for (let v in a) console.log(v);

Note: Overall, I recommend avoiding enumerating Arrays using for .. in. But this knowledge is still useful for extending prototypes of classes where enumeration is appropriate!