Jeder JS-Meinungsführer sagt, dass das Erweitern der nativen Objekte eine schlechte Praxis ist. Aber wieso? Bekommen wir einen Performance-Hit? Befürchten sie, dass jemand es "falsch" macht und unzählige Typen hinzufügt Object
, wodurch praktisch alle Schleifen eines Objekts zerstört werden?
Nehmen TJ Holowaychuk ‚s should.js zum Beispiel. Er fügt ein einfaches Getter zu Object
und alles funktioniert ( Quelle ).
Object.defineProperty(Object.prototype, 'should', {
set: function(){},
get: function(){
return new Assertion(Object(this).valueOf());
},
configurable: true
});
Das macht wirklich Sinn. Zum Beispiel könnte man verlängern Array
.
Array.defineProperty(Array.prototype, "remove", {
set: function(){},
get: function(){
return removeArrayElement.bind(this);
}
});
var arr = [0, 1, 2, 3, 4];
arr.remove(3);
Gibt es Argumente gegen die Erweiterung nativer Typen?
javascript
prototype
prototypal-inheritance
Buschtoens
quelle
quelle
Antworten:
Wenn Sie ein Objekt erweitern, ändern Sie sein Verhalten.
Das Ändern des Verhaltens eines Objekts, das nur von Ihrem eigenen Code verwendet wird, ist in Ordnung. Wenn Sie jedoch das Verhalten von etwas ändern, das auch von anderem Code verwendet wird, besteht das Risiko, dass Sie diesen anderen Code beschädigen.
Wenn Sie den Objekt- und Array-Klassen in Javascript Methoden hinzufügen, ist das Risiko, dass etwas beschädigt wird, aufgrund der Funktionsweise von Javascript sehr hoch. Langjährige Erfahrung hat mich gelehrt, dass diese Art von Zeug alle Arten von schrecklichen Fehlern in Javascript verursacht.
Wenn Sie ein benutzerdefiniertes Verhalten benötigen, ist es weitaus besser, eine eigene Klasse (möglicherweise eine Unterklasse) zu definieren, als eine native zu ändern. Auf diese Weise werden Sie überhaupt nichts kaputt machen.
Die Fähigkeit, die Funktionsweise einer Klasse zu ändern, ohne sie zu unterordnen, ist ein wichtiges Merkmal jeder guten Programmiersprache, das jedoch selten und mit Vorsicht verwendet werden muss.
quelle
.stgringify()
würde also als sicher angesehen werden?stringify()
Methode mit unterschiedlichem Verhalten hinzuzufügen ? Es ist wirklich nichts, was Sie in der täglichen Programmierung tun sollten ... nicht, wenn Sie nur hier und da ein paar Zeichen Code speichern möchten. Definieren Sie besser Ihre eigene Klasse oder Funktion, die Eingaben akzeptiert und sie stringifiziert. Die meisten Browser definierenJSON.stringify()
, am besten überprüfen Sie, ob dies vorhanden ist, und wenn nicht, definieren Sie es selbst.someError.stringify()
vorerrors.stringify(someError)
. Es ist unkompliziert und passt perfekt zum Konzept von js. Ich mache etwas, das speziell an ein bestimmtes ErrorObject gebunden ist.Es gibt keinen messbaren Nachteil wie einen Leistungseinbruch. Zumindest erwähnte niemand etwas. Das ist also eine Frage persönlicher Vorlieben und Erfahrungen.
Das Hauptargument: Es sieht besser aus und ist intuitiver: Syntaxzucker. Da es sich um eine typ- / instanzspezifische Funktion handelt, sollte sie speziell an diesen Typ / diese Instanz gebunden sein.
Das Hauptgegenargument: Code kann stören. Wenn lib A eine Funktion hinzufügt, kann es die Funktion von lib B überschreiben. Dies kann den Code sehr leicht beschädigen.
Beide haben einen Punkt. Wenn Sie sich auf zwei Bibliotheken verlassen, die Ihre Typen direkt ändern, erhalten Sie höchstwahrscheinlich fehlerhaften Code, da die erwartete Funktionalität wahrscheinlich nicht dieselbe ist. Da stimme ich voll und ganz zu. Makrobibliotheken dürfen die nativen Typen nicht manipulieren. Andernfalls werden Sie als Entwickler nie wissen, was wirklich hinter den Kulissen vor sich geht.
Und das ist der Grund, warum ich Bibliotheken wie jQuery, Unterstrich usw. nicht mag. Versteh mich nicht falsch; Sie sind absolut gut programmiert und wirken wie ein Zauber, aber sie sind groß . Sie verwenden nur 10% von ihnen und verstehen ungefähr 1%.
Deshalb bevorzuge ich einen atomistischen Ansatz , bei dem Sie nur das benötigen, was Sie wirklich brauchen. Auf diese Weise wissen Sie immer, was passiert. Die Mikrobibliotheken tun nur das, was Sie möchten, damit sie nicht stören. Wenn der Endbenutzer weiß, welche Funktionen hinzugefügt werden, kann die Erweiterung nativer Typen als sicher angesehen werden.
TL; DR Erweitern Sie im Zweifelsfall keine nativen Typen. Erweitern Sie einen nativen Typ nur, wenn Sie zu 100% sicher sind, dass der Endbenutzer dieses Verhalten kennt und möchte. Bearbeiten Sie in keinem Fall die vorhandenen Funktionen eines nativen Typs, da dies die vorhandene Schnittstelle beschädigen würde.
Wenn Sie den Typ erweitern möchten, verwenden Sie
Object.defineProperty(obj, prop, desc)
;Wenn Sie nicht können , verwenden Sie die Typenprototype
.Ich habe diese Frage ursprünglich gestellt, weil ich wollte
Error
, dass s über JSON gesendet werden kann. Also brauchte ich einen Weg, um sie zu fesseln.error.stringify()
fühlte sich viel besser an alserrorlib.stringify(error)
; Wie das zweite Konstrukt andeutet, arbeite ich anerrorlib
und nicht an sicherror
selbst.quelle
MyError extends Error
es einestringify
Methode haben kann , ohne mit anderen Unterklassen zu kollidieren. Sie müssen immer noch Fehler behandeln, die nicht von Ihrem eigenen Code generiert wurden, daher ist dies möglicherweise weniger nützlichError
als bei anderen.Das Überschreiben bereits erstellter Methoden verursacht mehr Probleme als es löst, weshalb in vielen Programmiersprachen häufig angegeben wird, diese Praxis zu vermeiden. Woher sollen Entwickler wissen, dass die Funktion geändert wurde?
In diesem Fall überschreiben wir keine bekannte JS-Kernmethode, sondern erweitern String. Ein Argument in diesem Beitrag erwähnte, wie der neue Entwickler wissen soll, ob diese Methode Teil des Kern-JS ist oder wo die Dokumente zu finden sind. Was würde passieren, wenn das Kernobjekt JS String eine Methode mit dem Namen großschreiben erhalten würde? ?
Was wäre, wenn Sie anstelle von Namen, die möglicherweise mit anderen Bibliotheken kollidieren, einen firmen- / app-spezifischen Modifikator verwenden würden, den alle Entwickler verstehen könnten?
Wenn Sie andere Objekte weiter erweitern würden, würden alle Entwickler sie in freier Wildbahn mit (in diesem Fall) uns begegnen , wodurch sie benachrichtigt würden, dass es sich um eine unternehmens- / app-spezifische Erweiterung handelt.
Dies eliminiert keine Namenskollisionen, verringert jedoch die Möglichkeit. Wenn Sie feststellen, dass das Erweitern von JS-Kernobjekten für Sie und / oder Ihr Team bestimmt ist, ist dies möglicherweise für Sie.
quelle
Meiner Meinung nach ist es eine schlechte Praxis. Der Hauptgrund ist die Integration. Zitieren sollte.js docs:
Wie kann der Autor das wissen? Was ist, wenn mein Spott-Framework dasselbe tut? Was ist, wenn meine Versprechen lib dasselbe tun?
Wenn Sie es in Ihrem eigenen Projekt tun, ist es in Ordnung. Aber für eine Bibliothek ist es ein schlechtes Design. Underscore.js ist ein Beispiel dafür, was richtig gemacht wurde:
quelle
_(arr).flatten()
Beispiel hat mich tatsächlich überzeugt, native Objekte nicht zu erweitern. Mein persönlicher Grund dafür war rein syntaktisch. Aber das befriedigt mein ästhetisches Gefühl :) Selbst wenn ich einen normaleren Funktionsnamen verwendefoo(native).coolStuff()
, um ihn in ein "erweitertes" Objekt umzuwandeln, sieht das syntaktisch großartig aus. Also danke dafür!Die Erweiterung von Prototypen von Einbauten ist in der Tat eine schlechte Idee. Mit ES2015 wurde jedoch eine neue Technik eingeführt, mit der das gewünschte Verhalten erzielt werden kann:
Verwenden von
WeakMap
s zum Verknüpfen von Typen mit integrierten PrototypenDie folgende Implementierung erweitert die Prototypen
Number
undArray
ohne sie zu berühren:Wie Sie sehen, können mit dieser Technik auch primitive Prototypen erweitert werden. Es verwendet eine Kartenstruktur und
Object
Identität, um Typen mit integrierten Prototypen zu verknüpfen.In meinem Beispiel wird eine
reduce
Funktion aktiviert, die nur einArray
einzelnes Argument erwartet , da sie die Informationen zum Erstellen eines leeren Akkumulators und zum Verketten von Elementen mit diesem Akkumulator aus den Elementen des Arrays selbst extrahieren kann.Bitte beachten Sie, dass ich den normalen
Map
Typ hätte verwenden können , da schwache Referenzen keinen Sinn machen, wenn sie lediglich eingebaute Prototypen darstellen, die niemals durch Müll gesammelt werden. AWeakMap
ist jedoch nicht iterierbar und kann nur überprüft werden, wenn Sie den richtigen Schlüssel haben. Dies ist eine gewünschte Funktion, da ich jegliche Form der Typreflexion vermeiden möchte.quelle
xs[0]
leeren Arrays.type(undefined).monoid ...
Object.prototype
diese Weise "hinzugefügte" Methode kann nicht für eine aufgerufen werdenArray
), und die Syntax ist erheblich länger / hässlicher, als wenn Sie einen Prototyp wirklich erweitert hätten. Es wäre fast immer einfacher, nur Dienstprogrammklassen mit statischen Methoden zu erstellen. Der einzige Vorteil dieses Ansatzes ist die begrenzte Unterstützung des Polymorphismus.Ein Grund mehr, warum Sie nicht sollten native Objekte erweitern :
Wir verwenden Magento, das prototype.js verwendet und viele Dinge auf nativen Objekten erweitert. Dies funktioniert einwandfrei, bis Sie sich für neue Funktionen entscheiden und hier große Probleme auftreten.
Wir haben Webcomponents auf einer unserer Seiten eingeführt, daher beschließt die webcomponents-lite.js, das gesamte (native) Ereignisobjekt im IE zu ersetzen (warum?). Dies bricht natürlich prototype.js, was wiederum Magento bricht. (Bis Sie das Problem gefunden haben, können Sie viele Stunden investieren, um es zurückzuverfolgen.)
Wenn Sie Ärger mögen, machen Sie weiter!
quelle
Ich sehe drei Gründe, dies nicht zu tun ( zumindest innerhalb einer Bewerbung ), von denen nur zwei in den vorhandenen Antworten hier angesprochen werden:
Object.defineProperty
, wodurch standardmäßig nicht aufzählbare Eigenschaften erstellt werden.Punkt 3 ist wohl der wichtigste. Durch Testen können Sie sicherstellen, dass Ihre Prototyp-Erweiterungen keine Konflikte mit den von Ihnen verwendeten Bibliotheken verursachen, da Sie entscheiden, welche Bibliotheken Sie verwenden. Dies gilt nicht für native Objekte, vorausgesetzt, Ihr Code wird in einem Browser ausgeführt. Wenn Sie
Array.prototype.swizzle(foo, bar)
heute definieren und morgen GoogleArray.prototype.swizzle(bar, foo)
zu Chrome hinzufügt , werden Sie wahrscheinlich mit einigen verwirrten Kollegen enden, die sich fragen, warum.swizzle
das Verhalten nicht mit dem übereinstimmt, was auf MDN dokumentiert ist.(Siehe auch die Geschichte, wie das Herumspielen von Mootools mit Prototypen, die sie nicht besaßen, die Umbenennung einer ES6-Methode erzwang, um eine Unterbrechung des Webs zu vermeiden .)
Dies kann vermieden werden, indem ein anwendungsspezifisches Präfix für Methoden verwendet wird, die nativen Objekten hinzugefügt werden (z. B. definieren
Array.prototype.myappSwizzle
stattArray.prototype.swizzle
), aber das ist irgendwie hässlich. Es ist genauso gut lösbar, indem eigenständige Dienstprogrammfunktionen verwendet werden, anstatt Prototypen zu erweitern.quelle
Perf ist auch ein Grund. Manchmal müssen Sie möglicherweise die Schlüssel durchlaufen. Es gibt verschiedene Möglichkeiten, dies zu tun
Ich benutze normalerweise,
for of Object.keys()
weil es das Richtige tut und relativ knapp ist, ohne den Scheck hinzufügen zu müssen.Aber es ist viel langsamer .
Nur zu erraten, dass der Grund
Object.keys
langsam ist, ist offensichtlich,Object.keys()
muss eine Zuordnung vornehmen. Tatsächlich muss AFAIK seitdem eine Kopie aller Schlüssel zuweisen.Es ist möglich, dass die JS-Engine eine Art unveränderliche Schlüsselstruktur verwendet, sodass
Object.keys(object)
eine Referenz zurückgegeben wird, die über unveränderliche Schlüssel iteriertobject.newProp
ein völlig neues Objekt für unveränderliche Schlüssel erstellt, aber was auch immer, es ist deutlich langsamer als bis zu 15xSogar überprüfen
hasOwnProperty
ist bis zu 2x langsamer.Der Sinn all dessen ist, dass Sie, wenn Sie über einen perfekter Code verfügen und Schlüssel durchlaufen müssen, diese verwenden können möchten,
for in
ohne anrufen zu müssenhasOwnProperty
. Sie können dies nur tun, wenn Sie nichts geändert habenObject.prototype
Beachten Sie, dass wenn Sie
Object.defineProperty
den Prototyp ändern, wenn die von Ihnen hinzugefügten Elemente nicht aufzählbar sind, sie das Verhalten von JavaScript in den oben genannten Fällen nicht beeinflussen. Zumindest in Chrome 83 beeinträchtigen sie leider die Leistung.Ich habe 3000 nicht aufzählbare Eigenschaften hinzugefügt, um zu versuchen, das Auftreten von Perf-Problemen zu erzwingen. Mit nur 30 Eigenschaften waren die Tests zu nahe, um festzustellen, ob es Auswirkungen auf die Leistung gab.
https://jsperf.com/does-adding-non-enumerable-properties-affect-perf
Firefox 77 und Safari 13.1 zeigten keinen Unterschied in der Leistung zwischen den Klassen Augmented und Unaugmented. Möglicherweise wird v8 in diesem Bereich behoben, und Sie können die Leistungsprobleme ignorieren.
Aber lassen Sie mich auch hinzufügen, dass es die Geschichte von gibt
Array.prototype.smoosh
. Die Kurzversion ist Mootools, eine beliebte Bibliothek, die selbst erstellt wurdeArray.prototype.flatten
. Als das Standardkomitee versuchte, einen Eingeborenen hinzuzufügen, stelltenArray.prototype.flatten
sie fest, dass dies nicht möglich war, ohne viele Websites zu beschädigen. Die Entwickler, die von der Pause erfuhren, schlugen vor, die es5-Methodesmoosh
als Witz zu bezeichnen, aber die Leute flippten aus, ohne zu verstehen, dass es ein Witz war. Sie entschieden sichflat
stattdessen fürflatten
Die Moral der Geschichte ist, dass Sie native Objekte nicht erweitern sollten. Wenn Sie dies tun, könnten Sie auf dasselbe Problem stoßen, bei dem Ihre Inhalte beschädigt werden. Wenn Ihre bestimmte Bibliothek nicht so beliebt ist wie MooTools, ist es unwahrscheinlich, dass die Browser-Anbieter das von Ihnen verursachte Problem umgehen. Wenn Ihre Bibliothek so populär wird, wäre es eine Art Mittel, alle anderen dazu zu zwingen, das von Ihnen verursachte Problem zu umgehen. Bitte erweitern Sie native Objekte nicht
quelle
hasOwnProperty
erfahren, ob sich die Eigenschaft im Objekt oder im Prototyp befindet. Mit derfor in
Schleife erhalten Sie jedoch nur aufzählbare Eigenschaften. Ich habe es gerade ausprobiert: Fügen Sie dem Array-Prototyp eine nicht aufzählbare Eigenschaft hinzu, die nicht in der Schleife angezeigt wird. Sie müssen den Prototyp nicht ändern, damit die einfachste Schleife funktioniert.