22

I've come across a few comments here and there about how it's frowned upon to modify a JavaScript object's prototype? I personally don't see how it could be a problem. For instance extending the Array object to have map and include methods or to create more robust Date methods?

Michał Perłakowski
  • 88,409
  • 26
  • 156
  • 177
colourandcode
  • 431
  • 1
  • 6
  • 13

5 Answers5

28

The problem is that prototype can be modified in several places. For example one library will add map method to Array's prototype and your own code will add the same but with another purpose. So one implementation will be broken.

bjornd
  • 22,397
  • 4
  • 57
  • 73
  • 1
    Similar namespace collisions also happen with jQuery plugins (which are monkey patches to jQuery itself): http://stackoverflow.com/q/5740974/479863 – mu is too short Jun 03 '11 at 05:56
  • 4
    In that regard, I feel it's the developer's fault if they decide to use to multiple libraries and not know what's going on in their code... but that does answer my question, thank you. – colourandcode Jun 03 '11 at 06:02
  • 11
    You would expect a developer to be aware of all the internal structures and implementations of a massive library such as JQuery or DoJo? I doubt if they would get anything implemented before the next release of the framework and they had to start again. The whole point of libraries and frameworks is that you only need to know how to use them. – James Anderson Jun 03 '11 at 06:09
  • 1
    It's hardly possible to know all methods and features of some, even one, library especially those which will be introduced in future versions. So to be sure in code consistency it's the best method to just avoid polluting any global scope, one of which is prototypes of standard objects. – bjornd Jun 03 '11 at 06:11
  • 1
    It's not that you'd have to know all the methods and features... it's knowing whether or not native objects are manipulated. For a big library like jQuery or Dojo that information is easily accessible. – colourandcode Jun 03 '11 at 06:19
  • For jQuery and Dojo it's really easy, just because they don't touch prototypes of standard object. – bjornd Jun 03 '11 at 06:34
  • If extending the built-in objects' prototypes you can follow a naming convention that would make it extremely unlikely that conflicts would occur. E.g., if my library is called nnnnnn's Magic Library my array map function could be `Array.prototype.nmlMap()`. Or `Array.prototype.NML.map()`. – nnnnnn Jun 03 '11 at 06:37
  • You can even set all methods of your library to `window.nmlMethodName`, `window.nmlMethodName2`, ... It is so useful... – bjornd Jun 03 '11 at 07:22
  • 1
    I feel like libraries should be checking for collisions as they modify object prototypes, and if collisions are found they should alert you that multiple conflicting prototype-extension approaches are being used... – Gershom Maes Oct 27 '18 at 20:47
  • Libraries especially should _never_ modify built-in objects' prototypes. If there happens to be a collision between your library and a different library, they can't both be used in one project until one of them is patched, which will require a breaking change. Checking for collisions before modifying the prototype will only make the problem immediately obvious to the user, it doesn't solve it in any way. Seriously, _don't do this_. – Vojtěch Strnad May 06 '23 at 13:43
11

Mostly because of namespace collisions. I know the Prototype framework has had many problems with keeping their names different from the ones included natively.

There are two major methods of providing utilities to people..

Prototyping

Adding a function to an Object's prototype. MooTools and Prototype do this.

Advantages:

  1. Super easy access.

Disadvantages:

  1. Can use a lot of system memory. While modern browsers just fetch an instance of the property from the constructor, some older browsers store a separate instance of each property for each instance of the constructor.
  2. Not necessarily always available.

What I mean by "not available" is this:

Imagine you have a NodeList from document.getElementsByTagName and you want to iterate through them. You can't do..

document.getElementsByTagName('p').map(function () { ... });

..because it's a NodeList, not an Array. The above will give you an error something like: Uncaught TypeError: [object NodeList] doesn't have method 'map'.

I should note that there are very simple ways to convert NodeList's and other Array-like Objects into real arrays.

Collecting

Creating a brand new global variable and stock piling utilities on it. jQuery and Dojo do this.

Advantages:

  1. Always there.
  2. Low memory usage.

Disadvantages:

  1. Not placed quite as nicely.
  2. Can feel awkward to use at times.

With this method you still couldn't do..

document.getElementsByTagName('p').map(function () { ... });

..but you could do..

jQuery.map(document.getElementsByTagName('p'), function () { ... });

..but as pointed out by Matt, in usual use, you would do the above with..

jQuery('p').map(function () { ... });

Which is better?

Ultimately, it's up to you. If you're OK with the risk of being overwritten/overwriting, then I would highly recommend prototyping. It's the style I prefer and I feel that the risks are worth the results. If you're not as sure about it as me, then collecting is a fine style too. They both have advantages and disadvantages but all and all, they usually produce the same end result.

McKayla
  • 6,879
  • 5
  • 36
  • 48
  • 1
    @tylerwashburn: *it has to store an instance of the function/string/whatever on every single instance.* Really? Afaik adding someting to the prototype adds it *once* in the constructor. Instances subsequently get their information from the constructors prototype. – KooiInc Jun 03 '11 at 06:21
  • You _can_ do `$('p').map(function () {...}` instead of either `map` example you mention. – Matt Ball Jun 03 '11 at 06:21
  • @KooiInc I'm not completely sure that it's true, but I've heard that older browsers do it. I'll update my answer to note that. – McKayla Jun 03 '11 at 06:24
  • @Matt It was to maintain consistency with the prototyping example. In actual practice, that would be the better way to do it. – McKayla Jun 03 '11 at 06:25
  • @tylerwashburn: I've never heard of that. Could you provide a link where it's mentioned? Now for the second part (*Not necessarily always available*): in what cases would an extension to `Object.prototype` not be available? – KooiInc Jun 03 '11 at 06:33
  • @KooiInc I never said anything about Object extensions not being available. The only constructor I really talked about that on was Array. Everything **should** inherit from JavaScript, but until IE9, document didn't inherit from Object. It didn't even have a constructor. – McKayla Jun 03 '11 at 06:38
  • @tylerwashburn: you did, as second disadvantage. I think you are mixing DOM scripting with javascript here, where in IE indeed html elements were misbehaving - their prototype not being extendable. Anyway, the only real contra to extending `Object.prototype` imho is the possibility for confusion if you use third party scripts. – KooiInc Jun 03 '11 at 06:54
  • @KooiInc You're confused. What that meant is that just because you change one constructor doesn't mean you change everything. `Function.prototype.hey = "hey"` would have no effect on a string. – McKayla Jun 03 '11 at 06:56
  • @tylerwashburn: you say: `Function.prototype.hey = "hey"` would have no effect on a string. It will ;~) Try this: `Function.prototype.hey = 'hey'; alert(String.hey)`; Anyway, an extention to some objects prototype is always available to that object and everything that uses its prototype chain, that's what I meant - and by the way demonstrated with `String.hey`. – KooiInc Jun 03 '11 at 07:15
  • @Kooilnc - that is because *String* is a function, so inherits from *Function.prototype*. However, *String* objects don't inherit from *Function.prototype* (they inherit from *String.prototype* -> *Object.prototype* -> *null*) so its properties aren't inherited by String objects. – RobG Jun 03 '11 at 10:15
  • @KooiInc I'm not talking about the constructor. I'm talking about ACTUAL STRINGS. You know? `"Hello World"` type strings. – McKayla Jun 03 '11 at 19:26
  • @RobG: yip, stupid of me. Had an `Object.prototype` extention in mind, that being after all the subject of OPs question. 'Object.prototype.hey = 'hey'; alert('hi'.hey);' *will* alert 'hey', as will `(213).hey`, `new SomeHomeBrewObject().hey`, `[].hey` etc. That by the way may be another quite valid argument to avoid too much `Object.prototype` augmentation – KooiInc Jun 03 '11 at 21:01
  • @tylerwashburn: let's end the debate, it's leading nowhere. You started your answer with namespace collisions, a valid contra. I didn't really follow your disadvantages of prototyping after that, and I still don't, sorry. Fortunately I'm always confused. It's a good base for learning stuff. – KooiInc Jun 03 '11 at 21:12
  • @KooiInc Most people avoid extending Object because of that reason. *Everything* get's changed. And it messes up `for (prop in obj)` loops. I always check that with `if (obj[prop] === obj.constructor.prototype[prop])` though. That way, if I have my own instance specific `toString` or something like that, it still gets run for, but not if it's the normal Object:toString. – McKayla Jun 03 '11 at 21:23
6

As bjornd pointed out, monkey-patching is a problem only when there are multiple libraries involved. Therefore its not a good practice to do it if you are writing reusable libraries. However, it still remains the best technique out there to iron out cross-browser compatibility issues when using host objects in javascript.

See this blog post from 2009 (or the Wayback Machine original) for a real incident when prototype.js and json2.js are used together.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
rahulmohan
  • 1,285
  • 11
  • 19
4

There is an excellent article from Nicholas C. Zakas explaining why this practice is not something that should be in the mind of any programmer during a team or customer project (maybe you can do some tweaks for educational purpose, but not for general project use).

Maintainable JavaScript: Don’t modify objects you don’t own: https://www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own/

Exel Gamboa
  • 936
  • 1
  • 14
  • 25
3

In addition to the other answers, an even more permanent problem that can arise from modifying built-in objects is that if the non-standard change gets used on enough sites, future versions of ECMAScript will be unable to define prototype methods using the same name. See here:

This is exactly what happened with Array.prototype.flatten and Array.prototype.contains. In short, the specification was written up for those methods, their proposals got to stage 3, and then browsers started shipping it. But, in both cases, it was found that there were ancient libraries which patched the built-in Array object with their own methods with the same name as the new methods, and had different behavior; as a result, websites broke, the browsers had to back out of their implementations of the new methods, and the specification had to be edited. (The methods were renamed.)

For example, there is currently a proposal for String.prototype.replaceAll. If you ship a library which gets widely used, and that library monkeypatches a custom non-standard method onto String.prototype.replaceAll, the replaceAll name will no longer be usable by the specification-writers; it will have to be changed before browsers can implement it.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    If I override replaceAll, but it uses the same method as the common replaceAll, and works with the exact same parameters, would there be any difference in end result? If nothing is broken, then would you simply have an early implementation? – SwiftNinjaPro Jan 09 '20 at 16:37
  • 1
    If the version you implement is *completely* spec-compliant, then that's perfectly fine - that's called a **polyfill**, and is extremely common. – CertainPerformance Jan 09 '20 at 20:38