Aus den MDN-Dokumenten für die StandardsetPrototypeOf
-Funktion sowie für die nicht standardmäßige __proto__
-Eigenschaft :
Es wird dringend davon abgeraten, den [Prototyp] eines Objekts zu verändern, egal wie dies ausgeführt wird, da er sehr langsam ist und die nachfolgende Ausführung in modernen JavaScript-Implementierungen unvermeidlich verlangsamt.
Die Verwendung von Function.prototype
zum Hinzufügen von Eigenschaften ist the Weg, um Mitgliedsfunktionen zu Javascript-Klassen hinzuzufügen. Dann wie das folgende zeigt:
function Foo(){}
function bar(){}
var foo = new Foo();
// This is bad:
//foo.__proto__.bar = bar;
// But this is okay
Foo.prototype.bar = bar;
// Both cause this to be true:
console.log(foo.__proto__.bar == bar); // true
Warum ist foo.__proto__.bar = bar;
schlecht? Wenn es schlecht ist, ist Foo.prototype.bar = bar;
nicht genauso schlecht?
Warum dann diese Warnung: es ist sehr langsam und verlangsamt die nachfolgende Ausführung in modernen JavaScript-Implementierungen unvermeidlich. Sicherlich ist Foo.prototype.bar = bar;
nicht so schlimm.
Update Vielleicht bedeuteten sie durch Mutation eine Neuzuordnung. Siehe akzeptierte Antwort.
// This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar;
Nein, beide machen dasselbe (als foo.__proto__ === Foo.prototype
) Und beide sind in Ordnung. Sie erstellen lediglich eine bar
-Eigenschaft für das Object.getPrototypeOf(foo)
-Objekt.
Die Anweisung verweist auf die Zuweisung der Eigenschaft __proto__
Selbst:
function Employee() {}
var fred = new Employee();
// Assign a new object to __proto__
fred.__proto__ = Object.prototype;
// Or equally:
Object.setPrototypeOf(fred, Object.prototype);
Die Warnung auf der Seite Object.prototype
wird ausführlicher beschrieben:
Das Mutieren des Prototyps eines Objekts ist aufgrund der Art und Weise, wie moderne JavaScript-Engines den Zugriff auf Eigenschaften optimieren , ein sehr langsamer Vorgang
Sie geben einfach an, dass die Prototypenkette ändern eines bereits vorhandenen Objekts Optimierungen töten. Stattdessen soll über Object.create()
ein neues Objekt mit einer anderen Prototypenkette erstellt werden.
Ich konnte keine explizite Referenz finden, aber wenn wir uns überlegen, wie versteckte Klassen von V8 implementiert werden, können wir sehen, was hier möglicherweise vor sich geht. Wenn Sie die Prototypkette eines Objekts ändern, ändert sich sein interner Typ. Sie wird nicht einfach zu einer Unterklasse wie beim Hinzufügen einer Eigenschaft, sondern wird vollständig ausgetauscht. Dies bedeutet, dass alle Optimierungen der Eigenschaftensuche gelöscht werden und vorkompilierter Code verworfen werden muss. Oder es wird einfach auf nicht optimierten Code zurückgegriffen.
Einige bemerkenswerte Zitate:
Brendan Eich (du kennst ihn) sagte
Beschreibbares __proto__ ist ein riesiger Aufwand für die Implementierung (muss für die Zyklusprüfung serialisiert werden) und erzeugt alle Arten von Gefahren für Typverwechslungen.
Brian Hackett (Mozilla) sagte :
Das Ermöglichen, dass Skripte den Prototypen von so gut wie jedem Objekt verändern, erschwert das Erkennen des Verhaltens eines Skripts und macht die Implementierung von VM, JIT und Analyse komplexer und fehlerhafter. Typinferenz hatte mehrere Fehler aufgrund von __proto__ und kann aufgrund dieser Funktion nicht mehrere wünschenswerte Invarianten verwalten (dh 'Typmengen enthalten alle möglichen Typobjekte, die für eine var/property realisiert werden können' und 'JSFunctions haben Typen, die auch Funktionen sind'). ).
Mutation des Prototyps nach der Erstellung mit seiner unregelmäßigen Leistungsdestabilisierung und den Auswirkungen auf Proxys und SetInheritance
Ich erwarte keine großen Leistungssteigerungen, wenn Proto nicht überschreibbar ist. In nicht optimiertem Code müssen Sie die Prototypkette überprüfen, falls die Prototypobjekte (nicht ihre Identität) geändert wurden. Bei optimiertem Code können Sie auf nicht optimierten Code zurückgreifen, wenn jemand in Proto schreibt. Zumindest bei der V8-Kurbelwelle würde das also nicht viel ausmachen.
Wenn Sie __proto__ setzen, ruinieren Sie nicht nur alle Chancen, die Sie für zukünftige Optimierungen von Ion für dieses Objekt gehabt haben, sondern Sie erzwingen auch, dass die Engine zu allen anderen Teilen der Typinferenz kriecht (Informationen zu Funktionsrückgabewerten, oder Eigenschaftswerte (vielleicht), die glauben, über dieses Objekt Bescheid zu wissen, und die sie auffordern, auch nicht viele Annahmen zu treffen, was eine weitere Deoptimierung und möglicherweise Ungültigmachung des vorhandenen Jitcodes mit sich bringt.
Das Ändern des Prototyps eines Objekts mitten in der Ausführung ist wirklich ein übler Vorschlaghammer, und wir müssen nur auf Nummer sicher gehen, aber auf Nummer sicher, wenn es langsam ist.
__proto__
/setPrototypeOf
ist nicht identisch mit der Zuweisung an den Objektprototyp. Zum Beispiel, wenn Sie eine Funktion/ein Objekt mit zugeordneten Mitgliedern haben:
function Constructor(){
if (!(this instanceof Constructor)){
return new Constructor();
}
}
Constructor.data = 1;
Constructor.staticMember = function(){
return this.data;
}
Constructor.prototype.instanceMember = function(){
return this.constructor.data;
}
Constructor.prototype.constructor = Constructor;
// By doing the following, you are almost doing the same as assigning to
// __proto__, but actually not the same :P
var newObj = Object.create(Constructor);// BUT newObj is now an object and not a
// function like !!!Constructor!!!
// (typeof newObj === 'object' !== typeof Constructor === 'function'), and you
// lost the ability to instantiate it, "new newObj" returns not a constructor,
// you have .prototype but can't use it.
newObj = Object.create(Constructor.prototype);
// now you have access to newObj.instanceMember
// but staticMember is not available. newObj instanceof Constructor is true
// we can use a function like the original constructor to retain
// functionality, like self invoking it newObj(), accessing static
// members, etc, which isn't possible with Object.create
var newObj = function(){
if (!(this instanceof newObj)){
return new newObj();
}
};
newObj.__proto__ = Constructor;
newObj.prototype.__proto__ = Constructor.prototype;
newObj.data = 2;
(new newObj()).instanceMember(); //2
newObj().instanceMember(); // 2
newObj.staticMember(); // 2
newObj() instanceof Constructor; // is true
Constructor.staticMember(); // 1
Jeder scheint sich nur auf den Prototyp zu konzentrieren und vergisst, dass Funktionen Mitglieder zugewiesen werden können, die nach der Mutation instanziiert werden. Es gibt derzeit keine andere Möglichkeit, dies zu tun, ohne __proto__
/setPrototypeOf
zu verwenden. Kaum jemand verwendet einen Konstruktor ohne die Möglichkeit, von einer übergeordneten Konstruktorfunktion zu erben, und Object.create
kann nicht ausgeführt werden.
Und außerdem sind das zwei Object.create
-Aufrufe, die in V8 (sowohl Browser als auch Node) im Moment unglücklich langsam sind, was __proto__
zu einer praktikabeleren Wahl macht
Ja .prototype = ist genauso schlecht, daher die Formulierung "egal, wie es ausgeführt wird". Prototyp ist ein Pseudoobjekt zur Erweiterung der Funktionalität auf Klassenebene. Seine dynamische Natur verlangsamt die Skriptausführung. Das Hinzufügen einer Funktion auf Instanzebene verursacht dagegen weit weniger Aufwand.
Hier ist ein Benchmark mit Knoten v6.11.1
NormalClass : Eine normale Klasse, deren Prototyp nicht bearbeitet wurde
PrototypeEdited : Eine Klasse, deren Prototyp bearbeitet wurde (die Funktion test()
wird hinzugefügt).
PrototypeReference : Eine Klasse mit der hinzugefügten Prototypfunktion test()
, die auf eine externe Variable verweist
Ergebnisse :
NormalClass x 71,743,432 ops/sec ±2.28% (75 runs sampled)
PrototypeEdited x 73,433,637 ops/sec ±1.44% (75 runs sampled)
PrototypeReference x 71,337,583 ops/sec ±1.91% (74 runs sampled)
Wie Sie sehen, ist die prototypisch bearbeitete Klasse wesentlich schneller als die normale Klasse. Der Prototyp, der über eine Variable verfügt, die auf eine externe Variable verweist, ist der langsamste. Dies ist jedoch eine interessante Möglichkeit, Prototypen mit bereits instantied-Variablen zu bearbeiten
Quelle :
const Benchmark = require('benchmark')
class NormalClass {
constructor () {
this.cat = 0
}
test () {
this.cat = 1
}
}
class PrototypeEdited {
constructor () {
this.cat = 0
}
}
PrototypeEdited.prototype.test = function () {
this.cat = 0
}
class PrototypeReference {
constructor () {
this.cat = 0
}
}
var catRef = 5
PrototypeReference.prototype.test = function () {
this.cat = catRef
}
function normalClass () {
var tmp = new NormalClass()
tmp.test()
}
function prototypeEdited () {
var tmp = new PrototypeEdited()
tmp.test()
}
function prototypeReference () {
var tmp = new PrototypeReference()
tmp.test()
}
var suite = new Benchmark.Suite()
suite.add('NormalClass', normalClass)
.add('PrototypeEdited', prototypeEdited)
.add('PrototypeReference', prototypeReference)
.on('cycle', function (event) {
console.log(String(event.target))
})
.run()