que javascript performance prototype prototype-chain

javascript - que - ¿Por qué mutar el[[prototipo]] de un objeto es malo para el rendimiento?



que es prototype en javascript (4)

Desde los documentos de MDN para la función setPrototypeOf estándar , así como la propiedad __proto__ no estándar:

No se recomienda mutar el [[prototipo]] de un objeto, sin importar cómo se logre esto, porque es muy lento e inevitablemente ralentiza la ejecución posterior en las implementaciones modernas de JavaScript.

El uso de Function.prototype para agregar propiedades es la forma de agregar funciones miembro a las clases de JavaScript. Entonces, como se muestra a continuación:

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

Por qué es foo.__proto__.bar = bar; ¿malo? Si es malo, no es Foo.prototype.bar = bar; igual de malo?

Entonces, ¿por qué esta advertencia: es muy lenta e inevitablemente ralentiza la ejecución posterior en las implementaciones de JavaScript modernas . Seguramente Foo.prototype.bar = bar; no es tan malo

Actualización Quizás por mutación significaron reasignación. Ver respuesta aceptada


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

No. Ambos están haciendo lo mismo (como foo.__proto__ === Foo.prototype ), y ambos están bien. Solo están creando una propiedad de bar en el objeto Object.getPrototypeOf(foo) .

A lo que se refiere la afirmación es a asignar a la propiedad __proto__ sí misma:

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

La advertencia en la página Object.prototype entra en más detalles:

Mutar el [[prototipo]] de un objeto es, por la naturaleza de cómo los motores de JavaScript modernos optimizar los accesos de propiedad , una operación muy lenta

Simplemente afirman que cambiar la cadena prototipo de un objeto ya existente elimina las optimizaciones . En cambio, se supone que debes crear un nuevo objeto con una cadena de prototipo diferente a través de Object.create() .

No pude encontrar una referencia explícita, pero si consideramos cómo se implementan las clases ocultas de V8 , podemos ver qué podría pasar aquí. Al cambiar la cadena prototipo de un objeto, su tipo interno cambia; no se convierte simplemente en una subclase, como cuando se agrega una propiedad, sino que se intercambia por completo. Significa que todas las optimizaciones de búsqueda de propiedades se vacían y el código precompilado se debe descartar. O simplemente recurre al código no optimizado.

Algunas citas notables:

  • Brendan Eich (lo conoces) dijo

    Escribir __proto__ es un gran dolor para implementar (se debe serializar para verificar el ciclo) y crea todo tipo de riesgos de confusión de tipo.

  • Brian Hackett (Mozilla) dijo :

    Permitir que las secuencias de comandos muten el prototipo de casi cualquier objeto hace que sea más difícil razonar sobre el comportamiento de una secuencia de comandos y hace que la implementación de VM, JIT y análisis sea más compleja y problemática. La inferencia de tipos ha tenido varios errores debido a __proto__ mutable y no puede mantener varias invariantes deseables debido a esta característica (es decir, ''los conjuntos de tipos contienen todos los posibles objetos de tipo que pueden realizarse para una var / propiedad'' y ''JSFunctions tienen tipos que también son funciones'' )

  • Jeff Walden dijo :

    Prototipo de mutación después de la creación, con su desestabilización de rendimiento errático y el impacto sobre los proxies y [[SetInheritance]]

  • Erik Corry (Google) dijo :

    No espero grandes ganancias de rendimiento al hacer proto no sobrescribible. En el código no optimizado, debe verificar la cadena del prototipo en caso de que se hayan cambiado los objetos del prototipo (no su identidad). En el caso de un código optimizado, puede recurrir al código no optimizado si alguien escribe en proto. Por lo tanto, no haría mucha diferencia, al menos en V8-Crankshaft.

  • Eric Faust (Mozilla) dijo

    Cuando configura __proto__, no solo está arruinando las posibilidades que pueda haber tenido para futuras optimizaciones de Ion en ese objeto, sino que también obliga al motor a moverse lentamente a todas las demás piezas de inferencia de tipo (información sobre valores de retorno de función, o valores de propiedad, tal vez) que piensan que saben sobre este objeto y les dicen que no hagan muchas suposiciones tampoco, lo que implica una mayor desoptimización y tal vez la invalidación de jitcode existente.
    Cambiar el prototipo de un objeto en el medio de la ejecución es realmente un mazo desagradable, y la única manera que tenemos de evitar cometer errores es ir a lo seguro, pero seguro es lento.


Aquí hay un punto de referencia usando el nodo v6.11.1

NormalClass : una clase normal, con el prototipo no editado

PrototypeEdited : una clase con el prototipo editado (se agrega la función test() )

PrototypeReference : una clase con la test() función de prototipo añadida test() que se refiere a una variable externa

Resultados:

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)

Como puede ver, la clase editada del prototipo es mucho más rápida que la clase normal. El prototipo que tiene una variable que hace referencia a uno externo es el más lento, pero esa es una forma interesante de editar prototipos con variables ya instanciadas

Fuente :

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


Sí .prototype = es igual de malo, de ahí la frase "no importa cómo se logre". prototype es un pseudoobjeto para extender la funcionalidad en el nivel de clase. Su naturaleza dinámica ralentiza la ejecución de scripts. Agregar una función en el nivel de instancia, por otro lado, incurre en mucha menos sobrecarga.


__proto__ / setPrototypeOf no es lo mismo que asignar al prototipo del objeto. Por ejemplo, cuando tiene una función / objeto con miembros asignados:

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

Todos parecen centrarse solo en el prototipo, y olvidan que las funciones pueden tener miembros asignados e instanciados después de la mutación. Actualmente no hay otra forma de hacerlo sin usar __proto__ / setPrototypeOf . Casi nadie usa un constructor sin la capacidad de heredar de una función de constructor padre y Object.create no se puede Object.create .

Y además, son dos llamadas Object.create , que en el momento actual, son terriblemente lentas en V8 (tanto el navegador como el nodo), lo que hace que __proto__ sea ​​una opción más viable