In regelmäßigen Abständen schlägt bei mir die Frage auf, ob es nicht in ganz bestimmten Fällen doch in Ordnung wäre, die Prototypen der nativen JavaScript- und DOM-Objekte zu erweitern. Meine Antwort darauf ist immer: in öffentlich zugänglichen Projekten, ist das ein absolutes No-Go. Gegen den Einsatz bei privaten Projekten oder bei Spezialtools wie Testframeworks (z.B. should.js) lassen sich auch Argumente finden, aber darüber kann man durchaus zweierlei Meinung sein.

Wenn es darum geht, Gründe gegen das Erweitern der nativen Built-Ins zu finden, wird oft die Kollisionsgefahr zuerst herausgekramt. Würde man in seinem Code Element.prototype.foo definieren, könnte eine Third-Party-Library jetzt oder in Zukunft auch etwas unter Element.prototype.foo implementieren wollen. Diesem Argument lässt sich entgegensetzen, dass das erstens nie passiert und sich zweitens in 99% aller Fälle problemlos reparieren lassen dürfte – einfach das eigene Element.prototype.foo in Element.prototype.foobar umbenennen und schon funktioniert wieder alles. Aber wenn man vom Start weg nicht den Namen Element.prototype.foo, sondern etwas wie Element.prototype.$_foo wählt, werden derartige Reparaturen höchstwahrscheinlich gar nicht erst nötig werden. Auf Projektebene ist die Kollisionsgefahr mit anderen Libraries praktisch nicht gegeben.

Das größere Problem mit dem Erweitern von nativen Objekten besteht nicht auf der Projekt-, sondern auf der Web-Ebene. Browser und Spezifikationen müssen bei der Entwicklung neuer Features immer darauf achten, dass die Neuheiten kompatibel zum gesamten existierenden, öffentlichen HTML/JavaScript/CSS-Code sind. Sie haben es also mit der denkbar größten Legacy-Codebase der Welt zu tun, nämlich dem kompletten WWW. Das macht die Entwicklung von Neuheiten recht knifflig, nicht zuletzt weil einige Entwickler nicht die Finger von den Prototypen nativer Elemente lassen konnten. Das beste Beispiel dafür ist die in ECMAScript 6 neu eingeführte Funktion Array.prototype.includes(), die prüft ob ein Array ein gegebenes Element enthält. Ursprünglich sollte das neue Feature den Namen Array.prototype.contains() tragen, doch es stellte sich heraus, dass mehrere Webseiten bereits länger eine genau so benannte, aber subtil anders implementierte Funktion gleichen Namens verwenden. Diese Funktion stammt aus der JavaScript-Library Mootools, die schon vor Jahren den Zenit ihrer Popularität überschritten hatte. Aber da es immer noch Webseiten gibt, die Mootools in der einen oder anderen Version verwenden, war der Name Array.prototype.contains() für ECMAScript 6 nicht zu gebrauchen. Es erfolgte die Umbenennung in includes() und in Folge musste natürlich auch die entsprechende String-Methode für ES6 umbenannt werden.

Das Beispiel zeigt, was passieren kann, wenn man als normaler Webentwickler die Prototypen von nativen Built-Ins erweitert: man fuhrwerkt im Legacy-Code jeder Browserengine und jeder Spezifikation herum, die es jemals gab und jemals geben wird! Und da man nicht weiß, in welche Richtung die Entwicklung der Webstandards in 3 Jahren gehen wird, sollte man tunlichst die Finger von den entsprechenden Objekten lassen. Vielleicht wird es nie ein Problem sein, sein eigenes Element.prototype.$_foo zu definieren. Vielleicht wird man genau damit aber auch ein Feature von DOM 6.0 oder ECMAScript 12 blockieren.

Zusammengefasst ist es in Ordnung, die nativen Prototypen zu erweitern, wenn entweder

  1. das Projekt privat ist (z.B. Intranet-App, Testing-Tool) und die entsprechenden Codezeilen auch niemals das WWW betreten werden, und sei es durch Copy & Paste in ein neues Projekt
  2. oder man sicher weiß, dass der entsprechende Code niemals mit irgendeinem zukünftigen Standard kollidieren wird oder dass zu dem Zeitpunkt, an dem die Kollision geschehen wird, garantiert vom fraglichen Code keine einzige Zeile mehr irgendwo im WWW aufzufinden ist.

Ich weiß nicht, wie man diese Bedingungen erfüllen will, ohne Hellseher zu sein. Daher lasse ich die Finger von den Prototypen der nativen Built-Ins und empfehle das auch jedem, der mich fragt.