webentwicklung-frage-antwort-db.com.de

Warum ist die Veränderung des [Prototyps] eines Objekts für die Leistung schlecht?

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. 

50
basarat
// 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'). ).

  • Jeff Walden sagte :

    Mutation des Prototyps nach der Erstellung mit seiner unregelmäßigen Leistungsdestabilisierung und den Auswirkungen auf Proxys und SetInheritance

  • Erik Corry (Google) sagte :

    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.

  • Eric Faust (Mozilla) sagte

    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.

55
Bergi

__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

2
pocesar

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.

1
Schien

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()
0