2011-09-16 10 views
7
// Case A 
function Constructor() { 
    this.foo = function() { 
    ... 
    }; 
    ... 
} 

// vs 
// Case B 
function Constructor() { 
    ... 
}; 

Constructor.prototype.foo = function() { 
    ... 
} 

Una de las principales razones por las que aconsejan el uso de prototipos es que .foo se crea una vez en el caso del prototipo donde como this.foo se crea varias veces cuando se utiliza el otro enfoque.lo hace la creación de funciones consumen más memoria

Sin embargo, uno esperaría que los intérpretes puedan optimizar esto. De modo que solo hay una copia de la función foo en el caso A.

Por supuesto, usted todavía tendría un contexto de alcance único para cada objeto debido a cierres pero que tiene menos sobrecarga que una nueva función para cada objeto.

¿Los intérpretes JS modernos optimizan el Caso A por lo que solo hay una copia de la función foo?

Respuesta

8

Sí, la creación de funciones usa más memoria.

... y, no, los intérpretes no optimizan el Caso A hasta una sola función.

El motivo es the JS scope chain requiere que cada instancia de una función capture las variables disponibles en el momento en que se crea. Dicho esto, modern interpreters son better sobre el caso A de lo que solían ser, pero en gran parte debido a que el rendimiento de las funciones de cierre era un problema conocido hace un par de años.

Mozilla dice avoid unnecessary closures por este motivo, pero los cierres son una de las herramientas más potentes y más utilizadas en el conjunto de herramientas de un desarrollador de JS.

Actualización: acaba de ejecutar this test que crea 'casos' 1M de Constructor, usando Node.js (que es V8, el intérprete JS en Chrome). Con caseA = true consigo este uso de la memoria:

{ rss: 212291584, 
vsize: 3279040512, 
heapTotal: 203424416, 
heapUsed: 180715856 } 

Y con caseA = false consigo este uso de la memoria:

{ rss: 73535488, 
vsize: 3149352960, 
heapTotal: 74908960, 
heapUsed: 56308008 } 

Así las funciones de cierre están definitivamente consumen significativamente más memoria, por casi 3 veces. Pero en el sentido absoluto, solo estamos hablando de una diferencia de ~ 140-150 bytes por instancia. (Sin embargo, es probable que aumente según la cantidad de variables dentro del alcance que tenga cuando se crea la función).

+0

Podemos tener algunas referencias que definen "mejor" y "intérpretes modernos" – Raynos

+0

Sus pruebas coinciden con lo que encontré en la mía también --- También agregué algunos bloques de código grandes dentro de la función para probar si eso hizo que el globo de memoria es más rápido, y no ... El código dentro de la función no requiere memoria extra. – gnarf

+0

Ah, y también desconecté el uso de la memoria antes y después de la prueba para medir el diff antes de que se crearan los objetos – gnarf

0

Los intérpretes de JavaScript tampoco optimizan los prototipos. Es simplemente un caso de que solo haya uno por tipo (esa referencia de instancias múltiples). Los constructores, por otro lado, crean nuevas instancias y los métodos definidos dentro de ellas. Entonces, por definición, esto realmente no es una cuestión de "optimización" del intérprete, sino de simplemente entender lo que está sucediendo.

En una nota lateral, si el intérprete intentara y consolidara los métodos de instancia, se encontraría con problemas si alguna vez decidiera cambiar el valor de uno en una instancia particular (preferiría que ese dolor de cabeza no se agregue al idioma)) :)

+0

el compilador optimiza la función, pero no optimiza el scopecontext. – Raynos

+2

No obstante, el modo en que cada intérprete maneja los duplicados no parece ser el problema, el intérprete deberá establecer una diferencia entre los métodos de instancia múltiple y esa diferencia consumirá más memoria – Marlin

+1

Para el caso A, esperaría que un compilador compruebe que hay no hay variables locales en el constructor, por lo que no es necesario mantener su objeto variable en la cadena de alcance de las instancias. Si se utilizan variables locales, no puede hacerlo a menos que sea lo suficientemente inteligente como para optimizar el objeto variable si la función interna no hace referencia a ellos. A pesar de todo, usaría el enfoque de prototipo como más ordenado y más fácil de mantener (IMO, por supuesto). – RobG

2

Creo que, después de algunas pruebas breves en el nodo, en los casos A y B solo hay una copia del código real para la función foo en la memoria.

Caso A: hay un objeto de función creado para cada ejecución del Constructor() que almacena una referencia al código de funciones y su alcance de ejecución actual.

Caso B: solo hay un alcance, un objeto de función, compartido a través de un prototipo.

+0

¿Puedes publicar tu código de prueba? Estoy seguro de que hay algo de optimización dentro del intérprete para evitar * analizar * el código de la función cada vez, pero cada paso a través del constructor tiene que capturar referencias a cualquier variable dentro del alcance para que puedan resolverse adecuadamente cuando la función es invocado. – broofa

+0

@broofa - comparando mi código con el suyo, es básicamente el mismo ... :) - Acabo de arrojar como 40 líneas de código de algo más dentro de la función para probar que ... – gnarf

Cuestiones relacionadas