Perché il mutamento ] di un object è negativo per le prestazioni?

Dai documenti MDN per la funzione standard __proto__ proprietà __proto__ non standard:

La mutazione del [[Prototipo]] di un object, indipendentemente dal modo in cui viene realizzata, è fortemente scoraggiata, poiché è molto lenta e inevitabilmente rallenta la successiva esecuzione nelle moderne implementazioni di JavaScript.

Usare Function.prototype per aggiungere proprietà è il modo per aggiungere funzioni membro alle classi javascript. Quindi, come mostrato di seguito:

 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 

Perché foo.__proto__.bar = bar; cattivo? Se il suo male non è Foo.prototype.bar = bar; altrettanto male?

Quindi perché questo avviso: è molto lento e inevitabilmente rallenta l’esecuzione successiva nelle moderne implementazioni di JavaScript . Sicuramente Foo.prototype.bar = bar; non è così male

Aggiornamento Forse per mutazione intendevano la riassegnazione. Vedi risposta accettata.

 // This is bad: //foo.__proto__.bar = bar; // But this is okay Foo.prototype.bar = bar; 

No. Entrambi stanno facendo la stessa cosa (come foo.__proto__ === Foo.prototype ), ed entrambi stanno bene. Stanno semplicemente creando una proprietà bar Object.getPrototypeOf(foo) .

Ciò a cui si riferisce la dichiarazione è l’assegnazione alla proprietà __proto__ stessa:

 function Employee() {} var fred = new Employee(); // Assign a new object to __proto__ fred.__proto__ = Object.prototype; // Or equally: Object.setPrototypeOf(fred, Object.prototype); 

L’avviso nella pagina Object.prototype va più nel dettaglio:

La mutazione del [[Prototipo]] di un object è, per la natura di come i moderni motori JavaScript ottimizzano gli accessi alle proprietà , un’operazione molto lenta

Semplicemente affermano che cambiare la catena del prototipo di un object già esistente uccide le ottimizzazioni . Invece, dovresti creare un nuovo object con una diversa catena di prototipi tramite Object.create() .

Non sono riuscito a trovare un riferimento esplicito, ma se consideriamo come vengono implementate le classi nascoste di V8 , possiamo vedere cosa potrebbe succedere qui. Quando si modifica la catena del prototipo di un object, il suo tipo interno cambia – non diventa semplicemente una sottoclass come quando si aggiunge una proprietà, ma viene completamente invertita. Significa che tutte le ottimizzazioni della ricerca di proprietà vengono svuotate e il codice precompilato deve essere eliminato. O semplicemente ricade sul codice non ottimizzato.

Alcune citazioni notevoli:

  • Brendan Eich (lo conosci) disse

    Il __proto__ scrivibile è un enorme problema da implementare (deve serializzare per controllare il ciclo) e crea ogni sorta di pericoli di confusione di tipo.

  • Brian Hackett (Mozilla) ha detto :

    Consentire agli script di modificare il prototipo di praticamente tutti gli oggetti rende più difficile ragionare sul comportamento di uno script e rende VM, JIT e l’implementazione di analisi più complesse e bugger. L’inferenza di tipo ha avuto diversi bug dovuti al mutabile __proto__ e non può mantenere molti invarianti desiderabili a causa di questa caratteristica (ad esempio “set di caratteri contengono tutti i possibili oggetti di tipo che possono essere realizzati per una var / proprietà” e “I tipi JS hanno tipi che sono anche funzioni” ).

  • Jeff Walden ha detto :

    Prototipo di mutazione dopo la creazione, con la sua incerta destabilizzazione delle prestazioni e l’impatto sui proxy e [[SetInheritance]]

  • Erik Corry (Google) ha dichiarato :

    Non mi aspetto grandi guadagni di prestazioni dal rendere proto non sovrascrivibile. Nel codice non ottimizzato è necessario controllare la catena del prototipo nel caso in cui gli oggetti prototipo (non la loro identity framework) siano stati modificati. Nel caso di codice ottimizzato, è ansible ricorrere al codice non ottimizzato se qualcuno scrive su proto. Quindi non farebbe molta differenza, almeno in V8-Crankshaft.

  • Eric Faust (Mozilla) ha detto

    Quando imposti __proto__, non solo stai rovinando le possibilità che potresti avere per le future ottimizzazioni di Ion su quell’object, ma costringi anche il motore a gattonare su tutti gli altri tipi di inferenza di tipo (informazioni sui valori di ritorno delle funzioni, o valori di proprietà, forse) che pensano di conoscere questo object e dicono loro di non fare molte assunzioni, il che comporta un’ulteriore deoptimizzazione e forse l’invalidazione di codice jit esistente.
    Cambiare il prototipo di un object nel mezzo dell’esecuzione è davvero una brutta mazza, e l’unico modo per evitare di sbagliare è quello di giocare sul sicuro, ma la sicurezza è lenta.

__proto__ / setPrototypeOf non sono gli stessi dell’assegnazione al prototipo dell’object. Ad esempio, quando hai una funzione / object con membri assegnati ad essa:

 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 

Sembra che tutti si concentrino solo sul prototipo e dimenticano che le funzioni possono essere assegnate a membri e istanziate dopo la mutazione. Al momento non esiste un altro modo per farlo senza usare __proto__ / setPrototypeOf . Quasi nessuno usa un costruttore senza la possibilità di ereditare da una funzione di costruzione padre e Object.create non riesce a servire.

E in più, si tratta di due chiamate Object.create , che al momento attuale sono incredibilmente lente in V8 (sia browser che Node), il che rende __proto__ una scelta più fattibile

Sì .prototype = è altrettanto sbagliato, da qui la formulazione “non importa come sia realizzata”. prototype è un pseudo object per estendere la funzionalità a livello di class. La sua natura dynamic rallenta l’esecuzione degli script. L’aggiunta di una funzione a livello di istanza, d’altra parte, comporta un sovraccarico molto inferiore.

Ecco un benchmark usando il nodo v6.11.1

NormalClass : una class normale, con il prototipo non modificato

PrototypeEdited : una class con il prototipo modificato (viene aggiunta la funzione test() )

PrototypeReference : una class con il test() funzione prototipo aggiunto test() che fa riferimento a una variabile esterna

Risultati:

 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) 

Come puoi vedere, il prototipo modificato della class è più veloce della normale class. Il prototipo che ha una variabile che si riferisce a uno esterno è il più lento, ma è un modo interessante per modificare i prototipi con la variabile già instantata

Fonte :

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