Das Material, dessen Übersetzung wir heute veröffentlichen, wurde von Matthias Binens und Benedict Meirer erstellt. Sie arbeiten an der
V8 JS-Engine bei Google. Dieser Artikel widmet sich einigen grundlegenden Mechanismen, die nicht nur für V8, sondern auch für andere Motoren charakteristisch sind. Durch die Kenntnis der internen Struktur solcher Mechanismen können die an der JavaScript-Entwicklung Beteiligten die Probleme der Codeleistung besser steuern. Insbesondere werden wir hier über die Funktionen der Engine-Optimierungs-Pipelines sprechen und darüber, wie der Zugriff auf die Eigenschaften von Prototypen von Objekten beschleunigt werden kann.

Codeoptimierungsstufen und Kompromisse
Der Prozess der Konvertierung der Texte von in JavaScript geschriebenen Programmen in geeigneten Code zur Ausführung sieht in verschiedenen Engines ungefähr gleich aus.
Der Prozess der Konvertierung von Quell-JS-Code in ausführbaren CodeDetails finden Sie
hier . Darüber hinaus sollte beachtet werden, dass die Pipelines zum Konvertieren von Quellcode in ausführbare Dateien auf verschiedenen Ebenen für verschiedene Engines sehr ähnlich sind, ihre Codeoptimierungssysteme sich jedoch häufig unterscheiden. Warum ist das so? Warum haben einige Motoren mehr Optimierungsstufen als andere? Es stellt sich heraus, dass die Engines auf die eine oder andere Weise Kompromisse eingehen müssen, was darin besteht, dass sie entweder schnell Code generieren können, der nicht der effizienteste, aber für die Ausführung geeignete ist, oder mehr Zeit damit verbringen, solchen Code zu erstellen. optimale Leistung erzielen.
Schnelle Vorbereitung des Codes für die Ausführung und optimierter Code, der länger dauert, aber schneller ausgeführt wirdDer Interpreter kann schnell Bytecode generieren, aber dieser Code ist normalerweise nicht sehr effizient. Der optimierende Compiler benötigt dagegen mehr Zeit, um den Code zu generieren, erhält aber am Ende optimierten, schnelleren Maschinencode.
In V8 wird dieses Modell zur Vorbereitung des Codes für die Ausführung verwendet. Der V8-Interpreter heißt Ignition und ist der schnellste der vorhandenen Interpreter (in Bezug auf die Ausführung des Quellbytecodes). Der optimierende V8-Compiler heißt TurboFan und ist für die Erstellung hochoptimierten Maschinencodes verantwortlich.
Zündinterpreter und TurboFan-OptimierungscompilerDer Kompromiss zwischen der Verzögerung beim Starten des Programms und der Ausführungsgeschwindigkeit ist der Grund dafür, dass einige JS-Engines zusätzliche Optimierungsstufen aufweisen. In SpiderMonkey gibt es beispielsweise zwischen dem Interpreter und dem optimierenden Compiler IonMonkey eine Zwischenebene, die vom Basis-Compiler dargestellt wird (in der Mozilla-
Dokumentation wird sie als "Baseline-Compiler" bezeichnet, "Baseline" ist jedoch kein Eigenname).
SpiderMonkey-CodeoptimierungsstufenDer Interpreter generiert schnell Bytecode, aber dieser Code wird relativ langsam ausgeführt. Der Basis-Compiler benötigt länger, um den Code zu generieren, aber dieser Code ist bereits schneller. Schließlich benötigt der optimierende IonMonkey-Compiler die meiste Zeit, um Maschinencode zu generieren, aber dieser Code kann sehr effizient ausgeführt werden.
Schauen wir uns ein bestimmtes Beispiel an und sehen wir uns an, wie Pipelines verschiedener Engines mit Code umgehen. In dem hier vorgestellten Beispiel gibt es eine „heiße“ Schleife, die Code enthält, der sich so oft wiederholt.
let result = 0; for (let i = 0; i < 4242424242; ++i) { result += i; } console.log(result);
V8 beginnt mit der Ausführung des Bytecodes im Ignition-Interpreter. Zu einem bestimmten Zeitpunkt stellt die Engine fest, dass der Code „heiß“ ist, und startet das TurboFan-Frontend, das Teil von TurboFan ist, das mit Profildaten arbeitet und eine grundlegende Maschinendarstellung des Codes erstellt. Die Daten werden dann zur weiteren Verbesserung an den TurboFan-Optimierer übergeben, der in einem separaten Stream arbeitet.
Hotcode-Optimierung in V8Während der Optimierung führt V8 weiterhin Bytecode in Ignition aus. Nach Abschluss des Optimierers verfügen wir über ausführbaren Maschinencode, der in Zukunft verwendet werden kann.
Die SpiderMonkey-Engine beginnt auch, Bytecode im Interpreter auszuführen. Es gibt jedoch eine zusätzliche Ebene, die vom Basis-Compiler dargestellt wird, was dazu führt, dass der "heiße" Code zuerst zu diesem Compiler gelangt. Es generiert den Basiscode im Hauptthread, der Übergang zur Ausführung dieses Codes erfolgt, wenn er fertig ist.
Hotcode-Optimierung in SpiderMonkeyWenn der Basiscode lange genug ausgeführt wird, startet SpiderMonkey schließlich das IonMonkey-Frontend und den Optimierer, was dem in V8 sehr ähnlich ist. Der Basiscode wird weiterhin als Teil des von IonMonkey durchgeführten Codeoptimierungsprozesses ausgeführt. Wenn die Optimierung abgeschlossen ist, wird daher der optimierte Code anstelle des Basiscodes ausgeführt.
Die Architektur der Chakra-Engine ist der Architektur von SpiderMonkey sehr ähnlich, aber Chakra strebt eine höhere Parallelität an, um ein Blockieren des Hauptthreads zu vermeiden. Anstatt Kompilierungsaufgaben im Hauptthread zu lösen, kopiert Chakra den Bytecode und die Profildaten, die der Compiler wahrscheinlich benötigt, und sendet sie an einen separaten Kompilierungsprozess.
Hot-Code-Optimierung in ChakraWenn der von SimpleJIT erstellte generierte Code bereit ist, führt die Engine ihn anstelle des Bytecodes aus. Dieser Vorgang wird wiederholt, um mit der Ausführung des von FullJIT erstellten Codes fortzufahren. Der Vorteil dieses Ansatzes besteht darin, dass die mit dem Kopieren von Daten verbundenen Pausen normalerweise viel kürzer sind als diejenigen, die durch den Betrieb eines vollwertigen Compilers (Front-End) verursacht werden. Das Minus dieses Ansatzes ist jedoch die Tatsache, dass heuristische Kopieralgorithmen möglicherweise einige Informationen übersehen, die für eine Art von Optimierung nützlich sein können. Hier sehen wir ein Beispiel für einen Kompromiss zwischen der Qualität des empfangenen Codes und den Verzögerungen.
In JavaScriptCore werden alle optimierenden Kompilierungsaufgaben parallel zum Hauptthread ausgeführt, der für die Ausführung des JavaScript-Codes verantwortlich ist. Es gibt jedoch keine Kopierphase. Stattdessen ruft der Hauptthread einfach Kompilierungsaufgaben in einem anderen Thread auf. Der Compiler verwendet dann ein komplexes Sperrschema, um auf Profildaten vom Hauptthread zuzugreifen.
Optimierung von "heißem" Code in JavaScriptCoreDer Vorteil dieses Ansatzes besteht darin, dass die erzwungene Blockierung des Hauptthreads durch die Ausführung von Codeoptimierungsaufgaben verringert wird. Die Nachteile dieser Architektur bestehen darin, dass ihre Implementierung die Lösung komplexer Aufgaben der Multithread-Datenverarbeitung erfordert und dass im Laufe der Arbeit zur Ausführung verschiedener Operationen auf Sperren zurückgegriffen werden muss.
Wir haben gerade die Kompromisse besprochen, die Engines eingehen müssen. Dabei haben wir zwischen der schnellen Codegenerierung mit Interpreten und der Erstellung von schnellem Code mit optimierten Compilern gewählt. Dies sind jedoch weit entfernt von allen Problemen, mit denen die Motoren konfrontiert sind. Speicher ist eine weitere Systemressource, bei der Sie auf Kompromisslösungen zurückgreifen müssen. Um dies zu demonstrieren, betrachten Sie ein einfaches JS-Programm, das Zahlen hinzufügt.
function add(x, y) { return x + y; } add(1, 2);
Hier ist der Bytecode der
add
, die vom Ignition-Interpreter in V8 generiert wurde:
StackCheck Ldar a1 Add a0, [0] Return
Sie können nicht auf die Bedeutung dieses Bytecodes eingehen, da sein Inhalt für uns nicht von besonderem Interesse ist. Die Hauptsache hier ist, dass es nur vier Anweisungen hat.
Wenn ein solcher Code „heiß“ ist, wird TurboFan verwendet, der den folgenden hochoptimierten Maschinencode generiert:
leaq rcx,[rip+0x0] movq rcx,[rcx-0x37] testb [rcx+0xf],0x1 jnz CompileLazyDeoptimizedCode push rbp movq rbp,rsp push rsi push rdi cmpq rsp,[r13+0xe88] jna StackOverflow movq rax,[rbp+0x18] test al,0x1 jnz Deoptimize movq rbx,[rbp+0x10] testb rbx,0x1 jnz Deoptimize movq rdx,rbx shrq rdx, 32 movq rcx,rax shrq rcx, 32 addl rdx,rcx jo Deoptimize shlq rdx, 32 movq rax,rdx movq rsp,rbp pop rbp ret 0x18
Wie Sie sehen können, ist das Codevolumen im Vergleich zum obigen Beispiel von vier Anweisungen sehr groß. Typischerweise ist Bytecode viel kompakter als Maschinencode und insbesondere optimierter Maschinencode. Andererseits wird ein Interpreter benötigt, um Bytecode auszuführen, und optimierter Code kann direkt auf dem Prozessor ausgeführt werden.
Dies ist einer der Hauptgründe, warum JavaScript-Engines nicht den gesamten Code optimieren. Wie wir bereits gesehen haben, nimmt das Erstellen von optimiertem Maschinencode viel Zeit in Anspruch, und außerdem benötigt das Speichern von optimiertem Maschinencode mehr Speicher, wie wir gerade herausgefunden haben.
Speichernutzung und OptimierungsstufeInfolgedessen können wir sagen, dass der Grund dafür, dass JS-Engines unterschiedliche Optimierungsstufen aufweisen, das grundlegende Problem der Wahl zwischen einer schnellen Codegenerierung, beispielsweise unter Verwendung eines Interpreters, und einer schnellen Codegenerierung ist, die mithilfe des optimierenden Compilers ausgeführt wird. Wenn wir über die in Engines verwendeten Ebenen der Codeoptimierung sprechen, können dem Code umso subtilere Optimierungen vorgenommen werden, je mehr davon vorhanden sind. Dies wird jedoch aufgrund der Komplexität der Engines und der zusätzlichen Belastung des Systems erreicht. Außerdem dürfen wir hier nicht vergessen, dass der Optimierungsgrad des Codes die Speichermenge beeinflusst, die dieser Code belegt. Aus diesem Grund versuchen JS-Engines, nur "heiße" Funktionen zu optimieren.
Optimierung des Zugriffs auf Eigenschaften von Objektprototypen
JavaScript-Engines optimieren den Zugriff auf Objekteigenschaften mithilfe von sogenannten Objektformularen (Shape) und Inline-Caches (Inline-Cache, IC). Details dazu finden Sie in
diesem Material. Um es auf den Punkt zu bringen, können wir sagen, dass die Engine die Form des Objekts getrennt von den Werten des Objekts speichert.
Objekte mit der gleichen FormDie Verwendung von Objektformen ermöglicht die Durchführung einer Optimierung, die als Inline-Caching bezeichnet wird. Durch die gemeinsame Verwendung von Objektformularen und Inline-Caches können Sie die wiederholten Vorgänge beim Zugriff auf die Eigenschaften von Objekten beschleunigen, die an derselben Stelle im Code ausgeführt werden.
Beschleunigen des Zugriffs auf eine ObjekteigenschaftKlassen und Prototypen
Nachdem wir nun wissen, wie Sie den Zugriff auf Objekteigenschaften in JavaScript beschleunigen können, werfen Sie einen Blick auf eine der jüngsten JavaScript-Innovationen - Klassen. So sieht die Klassendeklaration aus:
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } }
Obwohl es so aussieht, als würde in JS ein völlig neues Konzept erscheinen, sind Klassen eigentlich nur syntaktischer Zucker für das Prototypsystem zum Erstellen von Objekten, das in JavaScript immer vorhanden war:
function Bar(x) { this.x = x; } Bar.prototype.getX = function getX() { return this.x; };
Hier schreiben wir die Funktion in die
getX
Eigenschaft des
getX
Objekts. Diese Operation funktioniert genauso wie beim Erstellen der Eigenschaften eines anderen Objekts, da Prototypen in JavaScript Objekte sind. In Sprachen, die auf der Verwendung von Prototypen wie JavaScript basieren, werden Methoden, die von allen Objekten eines bestimmten Typs gemeinsam genutzt werden können, in Prototypen gespeichert, und die Felder einzelner Objekte werden in ihren Instanzen gespeichert.
Schauen wir uns an, was sozusagen hinter den Kulissen passiert, wenn wir eine neue Instanz des
Bar
Objekts erstellen und es dem konstanten
foo
zuweisen.
const foo = new Bar(true);
Nach dem Ausführen eines solchen Codes hat die Instanz des hier erstellten Objekts ein Formular, das eine einzelne Eigenschaft
x
. Der Prototyp des
foo
Objekts ist
Bar.prototype
, der zur Klasse
Bar
.
Objekt und sein PrototypBar.prototype
hat ein eigenes Formular, das eine einzelne
getX
Eigenschaft enthält, deren Wert eine Funktion ist, die beim Aufruf den Wert von
this.x
Der Prototyp Prototyp
Bar.prototype
ist
Object.prototype
, der Teil der Sprache ist.
Object.prototype
ist das
Object.prototype
des Prototypbaums, daher ist sein Prototyp
null
.
Nun wollen wir sehen, was passiert, wenn Sie ein anderes Objekt vom Typ
Bar
erstellen.
Mehrere Objekte des gleichen TypsWie Sie sehen können, verwenden sowohl das
foo
Objekt als auch das
qux
Objekt, die Instanzen der
Bar
Klasse sind, wie bereits erwähnt, dieselbe Form des Objekts. Beide verwenden denselben Prototyp - das
Bar.prototype
Objekt.
Zugriff auf Prototypeneigenschaften
Jetzt wissen wir also, was passiert, wenn wir eine neue Klasse deklarieren und instanziieren. Und was ist mit dem Aufruf der Methode des Objekts? Betrachten Sie das folgende Codefragment:
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX();
Ein Methodenaufruf kann als eine Operation verstanden werden, die aus zwei Schritten besteht:
const x = foo.getX(); // : const $getX = foo.getX; const x = $getX.call(foo);
Im ersten Schritt wird die Methode geladen, die nur eine Eigenschaft des Prototyps ist (dessen Wert die Funktion ist). Im zweiten Schritt wird mit
this
Menge eine Funktion aufgerufen. Betrachten Sie den ersten Schritt beim Laden der
getX
Methode aus dem
foo
Objekt:
Laden der getX-Methode aus dem foo-ObjektDie Engine analysiert das
foo
Objekt und stellt fest, dass es keine
getX
Eigenschaft in Form des
foo
Objekts gibt. Dies bedeutet, dass die Engine die Prototypenkette des Objekts betrachten muss, um diese Methode zu finden. Die Engine greift auf den Prototyp
Bar.prototype
und
Bar.prototype
die Objektform dieses Prototyps. Dort findet er die gewünschte Eigenschaft bei Offset 0. Als nächstes wird auf den in
Bar.prototype
gespeicherten Wert
Bar.prototype
, dort wird
JSFunction
getX
erkannt - und genau das suchen wir. Damit ist die Suche nach der Methode abgeschlossen.
Die Flexibilität von JavaScript ermöglicht das Ändern von Prototypketten. Zum Beispiel so:
const foo = new Bar(true); foo.getX(); // true Object.setPrototypeOf(foo, null); foo.getX(); // Uncaught TypeError: foo.getX is not a function
In diesem Beispiel rufen wir die Methode
foo.getX()
zweimal auf, aber jeder dieser Aufrufe hat eine völlig andere Bedeutung und ein anderes Ergebnis. Obwohl JavaScript-Prototypen nur Objekte sind, ist es für JS-Engines noch schwieriger, den Zugriff auf Prototypeneigenschaften zu beschleunigen, als den Zugriff auf ihre eigenen Eigenschaften gewöhnlicher Objekte zu beschleunigen.
Wenn wir uns reale Programme ansehen, stellt sich heraus, dass das Laden von Prototyp-Eigenschaften eine sehr häufige Operation ist. Es wird jedes Mal ausgeführt, wenn eine Methode aufgerufen wird.
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX();
Zuvor haben wir darüber gesprochen, wie Engines das Laden regulärer, benutzerdefinierter Eigenschaften von Objekten mithilfe von Objektformularen und Inline-Caches optimieren. Wie kann das Laden wiederholter Prototypeneigenschaften für Objekte mit derselben Form optimiert werden? Oben haben wir gesehen, wie Eigenschaften geladen werden.
Laden der getX-Methode aus dem foo-ObjektIn unserem Fall müssen Sie Folgendes wissen, um den Zugriff auf die Methode durch wiederholte Aufrufe zu beschleunigen:
- Die Form des
foo
Objekts enthält nicht die getX
Methode und ändert sich nicht. Dies bedeutet, dass das foo
Objekt nicht geändert wird, indem Eigenschaften hinzugefügt oder gelöscht oder die Attribute der Eigenschaften geändert werden. - Der
foo
Prototyp ist immer noch der ursprüngliche Bar.prototype
. Dies bedeutet, dass sich der Prototyp foo
nicht mit der Object.setPrototypeOf()
-Methode oder durch Zuweisen eines neuen Prototyps zur speziellen _proto_
Eigenschaft _proto_
. - Das
Bar.prototype
Formular enthält getX
und ändert sich nicht. Das heißt, Bar.prototype
nicht geändert, indem Eigenschaften gelöscht, hinzugefügt oder ihre Attribute geändert werden.
Im allgemeinen Fall bedeutet dies, dass wir 1 Überprüfung des Objekts selbst und 2 Überprüfungen für jeden Prototyp bis zu dem Prototyp durchführen müssen, in dem die gesuchte Eigenschaft gespeichert ist. Das heißt, Sie müssen 1 + 2N-Überprüfungen durchführen (wobei N die Anzahl der getesteten Prototypen ist), was in diesem Fall nicht so schlecht aussieht, da die Prototypenkette ziemlich kurz ist. Motoren müssen jedoch häufig mit viel längeren Prototypenketten arbeiten. Dies ist beispielsweise typisch für gewöhnliche DOM-Elemente. Hier ist ein Beispiel:
const anchor = document.createElement('a');
Hier haben wir
HTMLAnchorElement
und rufen seine
getAttribute()
-Methode auf. Die Prototypenkette dieses einfachen Elements, das einen HTML-Link darstellt, enthält 6 Prototypen! Die interessantesten DOM-Methoden befinden sich nicht in ihrem eigenen Prototyp
HTMLAnchorElement
. Sie befinden sich in Prototypen, die sich weiter unten in der Kette befinden.
PrototypketteDie Methode
getAttribute()
finden Sie in
Element.prototype
. Dies bedeutet, dass die Engine jedes Mal, wenn die Methode
anchor.getAttribute()
, gezwungen ist, die folgenden Aktionen auszuführen:
- Überprüft das
anchor
selbst auf getAttribute
. - Überprüfen, ob der direkte Prototyp des Objekts
HTMLAnchorElement.prototype
. HTMLAnchorElement.prototype
, dass HTMLAnchorElement.prototype
keine getAttribute
Methode hat.- Überprüfen, ob der nächste Prototyp
HTMLElement.prototype
. - Herauszufinden, dass es hier keine notwendige Methode gibt.
- Schließlich stellen Sie fest, dass der nächste Prototyp
Element.prototype
. - Finden Sie heraus, dass es eine
getAttribute
Methode gibt.
Wie Sie sehen, werden hier 7 Prüfungen durchgeführt. Da solcher Code in der Webprogrammierung sehr häufig vorkommt, verwenden Engines Optimierungen, um die Anzahl der zum Laden von Prototypeneigenschaften erforderlichen Überprüfungen zu verringern.
Wenn wir zu einem der vorherigen Beispiele zurückkehren, können wir uns daran erinnern, dass wir beim Aufrufen der
getX
Methode des
getX
Objekts drei Überprüfungen durchführen:
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const $getX = foo.getX;
Für jedes Objekt in der Prototypenkette bis zu dem Objekt, das die gewünschte Eigenschaft enthält, müssen wir die Form des Objekts überprüfen, um festzustellen, ob nicht das vorhanden ist, wonach wir suchen. Es wäre schön, wenn wir die Anzahl der Überprüfungen reduzieren könnten, indem wir die Prototypprüfung auf die Überprüfung auf das Vorhandensein oder Fehlen dessen reduzieren, wonach wir suchen. Dies macht die Engine mit einem einfachen Schritt: Anstatt den Prototyp-Link in der Instanz selbst zu speichern, speichert die Engine ihn in Form eines Objekts.
Prototyp-ReferenzspeicherJedes Formular enthält einen Link zu einem Prototyp. Dies bedeutet auch, dass sich der Motor bei
foo
Änderung des Prototyps
foo
in die neue Form des Objekts bewegt. Jetzt müssen wir nur noch die Form des Objekts auf das Vorhandensein einer Eigenschaft überprüfen und den Prototyp-Link schützen.
Dank dieses Ansatzes können wir die Anzahl der Überprüfungen von 1 + 2N auf 1 + N reduzieren, wodurch der Zugriff auf die Eigenschaften von Prototypen beschleunigt wird. Solche Operationen sind jedoch immer noch ziemlich ressourcenintensiv, da ein linearer Zusammenhang zwischen ihrer Anzahl und der Länge der Prototypenkette besteht. Die Motoren haben verschiedene Mechanismen implementiert, um sicherzustellen, dass die Anzahl der Überprüfungen nicht von der Länge der Prototypkette abhängt, ausgedrückt als Konstante. Dies gilt insbesondere in Situationen, in denen das Laden derselben Eigenschaft mehrmals ausgeführt wird.
ValidityCell-Eigenschaft
V8 bezieht sich auf die Formen von Prototypen speziell für den obigen Zweck. Jeder Prototyp hat eine eindeutige Form, die nicht mit anderen Objekten (insbesondere mit anderen Prototypen) geteilt wird, und jedem der Prototypobjektformen ist eine
ValidityCell
Eigenschaft zugeordnet.
ValidityCell-EigenschaftDiese Eigenschaft wird für ungültig erklärt, wenn der dem Formular zugeordnete Prototyp oder ein darüber liegender Prototyp geändert wird. Betrachten Sie diesen Mechanismus genauer.
Um die sequentiellen Operationen zum Laden von Eigenschaften von Prototypen zu beschleunigen, verwendet V8 einen Inline-Cache mit vier Feldern:
ValidityCell
,
Prototype
,
Shape
,
Offset
.
Inline-Cache-FelderWährend des „Aufwärmens“ des Inline-Caches beim ersten Ausführen des Codes merkt sich V8 den Versatz, bei dem die Eigenschaft im Prototyp gefunden wurde, den Prototyp, in dem die Eigenschaft gefunden wurde (in diesem Beispiel
Bar.prototype
), die Form des Objekts (in diesem Fall
foo
). und zusätzlich eine Verknüpfung mit dem aktuellen
ValidityCell
Parameter des unmittelbaren Prototyps, eine Verknüpfung in Form eines Objekts (in diesem Fall auch
Bar.prototype
).
Wenn Sie das nächste Mal auf den Inline-Cache zugreifen, muss die Engine die Form des Objekts und der
ValidityCell
überprüfen. Wenn
ValidityCell
noch gültig ist, kann die Engine den zuvor im Prototyp gespeicherten Offset direkt nutzen, ohne zusätzliche Suchvorgänge auszuführen.
Wenn sich der Prototyp ändert, wird ein neues Formular erstellt und die vorherige
ValidityCell
Eigenschaft wird für ungültig erklärt. Wenn Sie das nächste Mal versuchen, auf den Inline-Cache zuzugreifen, hat dies keine Vorteile, was zu einer schlechten Leistung führt.
Die Folgen einer Änderung des PrototypsWenn wir mit dem DOM-Element zum Beispiel zurückkehren, bedeutet dies, dass jede Änderung, beispielsweise am Prototyp von
Object.prototype
, nicht nur dazu führt, dass der Inline-Cache für
Object.prototype
selbst ungültig wird, sondern auch für alle Prototypen, die sich in der Prototypenkette darunter befinden einschließlich
EventTarget.prototype
,
Node.prototype
,
Element.prototype
usw. bis hin zu
HTMLAnchorElement.prototype
.
Auswirkungen der Änderung des Object.prototypeDas Ändern des
Object.prototype
während der Codeausführung bedeutet in der Tat, dass die Leistung ernsthaft
Object.prototype
. Tu das nicht.
Wir untersuchen das Obige anhand eines Beispiels. Angenommen, wir haben die
Bar
Klasse und die
loadX
Funktion, die die Methode von Objekten
loadX
, die aus der
Bar
Klasse erstellt wurden. Wir rufen die
loadX
Funktion mehrmals auf und übergeben ihr Instanzen derselben Klasse.
function loadX(bar) { return bar.getX(); // IC 'getX' `Bar`. } loadX(new Bar(true)); loadX(new Bar(false)); // IC `loadX` `ValidityCell` // `Bar.prototype`. Object.prototype.newMethod = y => y; // `ValidityCell` IC `loadX` // `Object.prototype` .
Der
loadX
Cache in
loadX
jetzt auf
ValidityCell
für
Bar.prototype
. , ,
Object.prototype
— JavaScript,
ValidityCell
, - , .
Object.prototype
— , - , . , :
Object.prototype.foo = function() { };
Object.prototype
, - , . , . - , . , « », , .
, , . .
Object.prototype
, , - .
, — , JS- - , . . , , . , , , .
Zusammenfassung
, JS- , , , -,
ValidityCell
, . JavaScript, , ( , , , ).
Liebe Leser! , - , JS, ?
