herencia - prototype javascript tutorial
Rendimiento del operador de prototipo Javascript: ahorra memoria, pero ¿es más rápido? (9)
Por lo tanto, crear la propiedad en la instancia daría como resultado un tiempo de acceso ligeramente más rápido, pero esto podría ser solo un problema para una jerarquía de prototipos muy profunda.
En realidad, el resultado es diferente de lo que podríamos esperar: el tiempo de acceso a los métodos de prototipos es más rápido que el acceso a los métodos conectados exactamente al objeto (FF probado).
Leí aquí (Douglas Crockford) usando un operador de prototipos para agregar métodos a las clases de Javascript y también a la memoria .
Luego leo en este artículo de John Resig: "Instanciar una función con un montón de propiedades de prototipo es muy, muy, rápido " , pero ¿está hablando de usar un prototipo de la manera estándar, o está hablando sobre su ejemplo específico en su artículo?
Por ejemplo, está creando este objeto:
function Class1()
{
this.showMsg = function(string) { alert(string); }
}
var c = new Class1();
c.showMsg();
más lento que crear este objeto, entonces?
function Class1() {}
Class1.prototype.showMsg = function(string) { alert(string); }
var c = new Class1();
c.showMsg();
PD
Sé que el prototipo se usa para crear objetos de herencia y de singleton, etc. Pero esta pregunta no tiene nada que ver con estos temas.
EDITAR: a quien podría interesarle también la comparación del rendimiento entre un objeto JS y un objeto estático JS puede leer esta respuesta a continuación . Los objetos estáticos son definitivamente más rápidos , obviamente se pueden usar solo cuando no se necesita más de una instancia del objeto.
Necesitamos separar la construcción y el uso del objeto.
Al declarar una función en un prototipo, se comparte entre todas las instancias. Al declarar una función en un constructor, esto se recrea cada vez que se realiza una nueva instancia. Teniendo eso en cuenta, debemos comparar la construcción y el uso por separado para obtener mejores resultados. Eso es lo que hice y quiero compartir los resultados contigo. Este punto de referencia no prueba la velocidad de construcción.
function ThisFunc() {
this.value = 0;
this.increment = function(){
this.value++;
}
}
function ProtFunc() {
this.value = 0;
}
ProtFunc.prototype.increment = function (){
this.value++;
}
function ClosFunc() {
var value = 0;
return {
increment:function(){
value++;
}
};
}
var thisInstance = new ThisFunc;
var iterations = 1000000;
var intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
thisInstance.increment();
}
console.log(`ThisFunc: ${(new Date()).getTime() - intNow}`); // 27ms node v4.6.0
var protInstance = new ProtFunc;
intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
protInstance.increment();
}
console.log(`ProtFunc: ${(new Date()).getTime() - intNow}`); // 4ms node v4.6.0
var closInstance = ClosFunc();
intNow = (new Date()).getTime();
for (i = 0; i < iterations; i++) {
closInstance.increment();
}
console.log(`ClosFunc: ${(new Date()).getTime() - intNow}`); // 7ms node v4.6.0
A partir de estos resultados, podemos ver que la versión del prototipo es la más rápida (4 ms), pero la versión de cierre está muy cerca (7 ms). Es posible que aún necesite un punto de referencia para su caso particular.
Asi que:
- Podemos usar la versión prototipo cuando necesitamos tener todo el rendimiento o compartir funciones entre instancias.
- Podemos usar otras versiones cuando lo que queremos son las características que proporcionan. (encapsulación de estado privado, legibilidad, etc.)
PD: utilicé la respuesta de Andrew como referencia. Usó los mismos bucles y notación.
Pruebas API de rendimiento del navegador de alta resolución
Ninguna de las pruebas aquí se está aprovechando de la API de rendimiento para las pruebas de alta resolución, así que escribí una que mostrará los resultados actuales más rápidos para muchos escenarios diferentes, incluidos 2 que son más rápidos que cualquiera de las otras respuestas en la mayoría de las ejecuciones.
Ayunó en cada categoría (10,000 iteraciones)
- Acceso a la propiedad solamente (~ 0.5ms) :
{ __proto__: Type }
- Creación de objetos en bucle con acceso a propiedades (<3ms) :
Object.create(Type)
El código usa ES6 sin transposición de babel para garantizar la precisión. Funciona en Chrome actual. Ejecute la prueba a continuación para ver el desglose.
function profile () {
function test ( name
, define
, construct
, { index = 0
, count = 10000
, ordinals = [ 0, 1 ]
, constructPrior = false
} = {}
) {
performance.clearMarks()
performance.clearMeasures()
const symbols = { type: Symbol(''type'') }
const marks = (
{ __proto__: null
, start: `${name}_start`
, define: `${name}_define`
, construct: `${name}_construct`
, end: `${name}_end`
}
)
performance.mark(marks.start)
let Type = define()
performance.mark(marks.define)
let obj = constructPrior ? construct(Type) : null
do {
if(!constructPrior)
obj = construct(Type)
if(index === 0)
performance.mark(marks.construct)
const measureOrdinal = ordinals.includes(index)
if(measureOrdinal)
performance.mark(`${name}_ordinal_${index}_pre`)
obj.message(''hi'')
obj.addition(index, 2)
if(measureOrdinal)
performance.mark(`${name}_ordinal_${index}_post`)
} while (++index < count)
performance.mark(marks.end)
const measureMarks = Object.assign (
{ [`${name}_define`]: [ marks.start, marks.define ]
, [`${name}_construct`]: [ marks.define, marks.construct ]
, [`${name}_loop`]: [ marks.construct, marks.end ]
, [`${name}_total`]: [ marks.start, marks.end ]
}
, ordinals.reduce((reduction, i) => Object.assign(reduction, { [`${name}_ordinal_${i}`]: [ `${name}_ordinal_${i}_pre`, `${name}_ordinal_${i}_post` ] }), {})
)
Object.keys(measureMarks).forEach((key) => performance.measure(key, ...measureMarks[key]))
const measures = performance.getEntriesByType(''measure'').map(x => Object.assign(x, { endTime: x.startTime + x.duration }))
measures.sort((a, b) => a.endTime - b.endTime)
const durations = measures.reduce((reduction, measure) => Object.assign(reduction, { [measure.name]: measure.duration }), {})
return (
{ [symbols.type]: ''profile''
, profile: name
, duration: durations[`${name}_total`]
, durations
, measures
}
)
}
const refs = (
{ __proto__: null
, message: function(s) { var mymessage = s + '''' }
, addition: function(i, j) { return (i *2 + j * 2) / 2 }
}
)
const testArgs = [
[ ''constructor''
, function define() {
return function Type () {
this.message = refs.message
this.addition = refs.addition
}
}
, function construct(Type) {
return new Type()
}
]
, [ ''prototype''
, function define() {
function Type () {
}
Type.prototype.message = refs.message
Type.prototype.addition = refs.addition
return Type
}
, function construct(Type) {
return new Type()
}
]
, [ ''Object.create''
, function define() {
return (
{ __proto__: null
, message: refs.message
, addition: refs.addition
}
)
}
, function construct(Type) {
return Object.create(Type)
}
]
, [ ''proto''
, function define() {
return (
{ __proto__: null
, message: refs.message
, addition: refs.addition
}
)
}
, function construct(Type) {
return { __proto__: Type }
}
]
]
return testArgs.reduce(
(reduction, [ name, ...args ]) => (
Object.assign( reduction
, { [name]: (
{ normal: test(name, ...args, { constructPrior: true })
, reconstruct: test(`${name}_reconstruct`, ...args, { constructPrior: false })
}
)
}
)
)
, {})
}
let profiled = profile()
const breakdown = Object.keys(profiled).reduce((reduction, name) => [ ...reduction, ...Object.keys(profiled[name]).reduce((r, type) => [ ...r, { profile: `${name}_${type}`, duration: profiled[name][type].duration } ], []) ], [])
breakdown.sort((a, b) => a.duration - b.duration)
try {
const Pre = props => React.createElement(''pre'', { children: JSON.stringify(props.children, null, 2) })
ReactDOM.render(React.createElement(Pre, { children: { breakdown, profiled } }), document.getElementById(''profile''))
} catch(err) {
console.error(err)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="profile"></div>
Así que decidí probar esto también. Probé el tiempo de creación, el tiempo de ejecución y el uso de la memoria. Usé Nodejs v0.8.12 y el framework de prueba mocha que se ejecuta en un Mac Book Pro arrancaba en Windows 7. Los resultados ''rápidos'' usan prototipos y los ''lentos'' usan patrones de módulos. Creé 1 millón de cada tipo de objeto y luego accedí a los 4 métodos en cada objeto. Aquí están los resultados:
c:/ABoxAbove>mocha test/test_andrew.js
Fast Allocation took:170 msec
·Fast Access took:826 msec
state[0] = First0
Free Memory:5006495744
·Slow Allocation took:999 msec
·Slow Access took:599 msec
state[0] = First0
Free Memory:4639649792
Mem diff:358248k
Mem overhead per obj:366.845952bytes
? 4 tests complete (2.6 seconds)
El código es el siguiente:
var assert = require("assert"), os = require(''os'');
function Fast (){}
Fast.prototype = {
state:"",
getState:function (){return this.state;},
setState:function (_state){this.state = _state;},
name:"",
getName:function (){return this.name;},
setName:function (_name){this.name = _name;}
};
function Slow (){
var state, name;
return{
getState:function (){return this.state;},
setState:function (_state){this.state = _state;},
getName:function (){return this.name;},
setName:function (_name){this.name = _name;}
};
}
describe(''test supposed fast prototype'', function(){
var count = 1000000, i, objs = [count], state = "First", name="Test";
var ts, diff, mem;
it (''should allocate a bunch of objects quickly'', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){objs[i] = new Fast ();}
diff = Date.now () - ts;
console.log ("Fast Allocation took:%d msec", diff);
done ();
});
it (''should access a bunch of objects quickly'', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){
objs[i].setState (state + i);
assert (objs[i].getState () === state + i, "States should be equal");
objs[i].setName (name + i);
assert (objs[i].getName () === name + i, "Names should be equal");
}
diff = Date.now() - ts;
console.log ("Fast Access took:%d msec", diff);
console.log ("state[0] = " + objs[0].getState ());
mem = os.freemem();
console.log ("Free Memory:" + mem + "/n");
done ();
});
it (''should allocate a bunch of objects slowly'', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){objs[i] = Slow ();}
diff = Date.now() - ts;
console.log ("Slow Allocation took:%d msec", diff);
done ();
});
it (''should access a bunch of objects slowly'', function (done){
ts = Date.now ();
for (i = 0; i < count; ++i){
objs[i].setState (state + i);
assert (objs[i].getState () === state + i, "States should be equal");
objs[i].setName (name + i);
assert (objs[i].getName () === name + i, "Names should be equal");
}
diff = Date.now() - ts;
console.log ("Slow Access took:%d msec", diff);
console.log ("state[0] = " + objs[0].getState ());
var mem2 = os.freemem();
console.log ("Free Memory:" + mem2 + "/n");
console.log ("Mem diff:" + (mem - mem2) / 1024 + "k");
console.log ("Mem overhead per obj:" + (mem - mem2) / count + ''bytes'');
done ();
});
});
Conclusión: Esto respalda lo que otros en esta publicación han encontrado. Si constantemente creas objetos, entonces el mecanismo prototipo es claramente más rápido. Si su código pasa la mayor parte del tiempo accediendo a objetos, entonces el patrón del módulo es más rápido. Si tiene dudas sobre el uso de la memoria, el mecanismo del prototipo utiliza ~ 360 bytes menos por objeto.
Era una pregunta interesante, así que realicé algunas pruebas muy simples (debería haber reiniciado mis navegadores para borrar la memoria, pero no lo hice; tomar esto por lo que vale). Parece que al menos en Safari y Firefox, el prototype
ejecuta significativamente más rápido [edit: no 20x como se dijo anteriormente]. Estoy seguro de que una prueba del mundo real con objetos con todas las funciones sería una mejor comparación. El código que ejecuté fue el siguiente (realicé las pruebas varias veces, por separado):
var X,Y, x,y, i, intNow;
X = function() {};
X.prototype.message = function(s) { var mymessage = s + "";}
X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }
Y = function() {
this.message = function(s) { var mymessage = s + "";}
this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
};
intNow = (new Date()).getTime();
for (i = 0; i < 1000000; i++) {
y = new Y();
y.message(''hi'');
y.addition(i,2)
}
console.log((new Date()).getTime() - intNow); //FF=5206ms; Safari=1554
intNow = (new Date()).getTime();
for (i = 0; i < 1000000; i++) {
x = new X();
x.message(''hi'');
x.addition(i,2)
}
console.log((new Date()).getTime() - intNow);//FF=3894ms;Safari=606
Es una verdadera lástima, porque realmente odio usar prototype
. Me gusta que mi código de objeto sea autoencapsulado y no permita que se desplace. Supongo que cuando la velocidad importa, sin embargo, no tengo otra opción. Maldito.
[Editar] Muchas gracias a @Kevin, quien señaló que mi código anterior era incorrecto, dando un gran impulso a la velocidad informada del método prototype
. Después de la reparación, el prototipo sigue siendo significativamente más rápido, pero la diferencia no es tan grande.
Estoy seguro de que, en lo que respecta a instanciar el objeto, es mucho más rápido y también consume menos memoria, no hay dudas al respecto, pero creo que el motor de JavaScript necesita recorrer todas las propiedades del objeto para determinar si la propiedad / El método invocado es parte de ese objeto y, en caso negativo, busque el prototipo. No estoy 100% seguro de esto, pero supongo que así es como funciona, y en ese caso, en ALGUNOS casos en los que su objeto tiene MUCHOS métodos agregados, instanciado solo una vez y utilizado en gran medida, entonces podría ser una Un poco más lento, pero eso es solo una suposición. No he probado nada.
Pero al final, todavía estoy de acuerdo en que, como regla general, el uso del prototipo será más rápido.
Intuitivamente, parece que sería más eficiente en cuanto a la memoria y más rápido crear funciones en el prototipo: la función solo se crea una vez, no cada vez que se crea una nueva instancia.
Sin embargo, habrá una ligera diferencia de rendimiento cuando llegue el momento de acceder a la función. Cuando se hace referencia a c.showMsg
, el tiempo de ejecución de JavaScript comprueba primero la propiedad en c
. Si no se encuentra, se comprueba el prototipo de c
.
Por lo tanto, crear la propiedad en la instancia daría como resultado un tiempo de acceso ligeramente más rápido, pero esto podría ser solo un problema para una jerarquía de prototipos muy profunda.
Supongo que depende del tipo de objeto que quiera crear. Ejecuté una prueba similar a la de Andrew, pero con un objeto estático y el objeto estático ganó sin problemas. Aquí está la prueba:
var X,Y,Z,x,y,z;
X = function() {};
X.prototype.message = function(s) { var mymessage = s + "";}
X.prototype.addition = function(i,j) { return (i *2 + j * 2) / 2; }
Y = function() {
this.message = function(s) { var mymessage = s + "";}
this.addition = function(i,j) { return (i *2 + j * 2) / 2; }
};
Z = {
message: function(s) { var mymessage = s + "";}
,addition: function(i,j) { return (i *2 + j * 2) / 2; }
}
function TestPerformance()
{
var closureStartDateTime = new Date();
for (var i = 0; i < 100000; i++)
{
y = new Y();
y.message(''hi'');
y.addition(i,2);
}
var closureEndDateTime = new Date();
var prototypeStartDateTime = new Date();
for (var i = 0; i < 100000; i++)
{
x = new X();
x.message(''hi'');
x.addition(i,2);
}
var prototypeEndDateTime = new Date();
var staticObjectStartDateTime = new Date();
for (var i = 0; i < 100000; i++)
{
z = Z; // obviously you don''t really need this
z.message(''hi'');
z.addition(i,2);
}
var staticObjectEndDateTime = new Date();
var closureTime = closureEndDateTime.getTime() - closureStartDateTime.getTime();
var prototypeTime = prototypeEndDateTime.getTime() - prototypeStartDateTime.getTime();
var staticTime = staticObjectEndDateTime.getTime() - staticObjectStartDateTime.getTime();
console.log("Closure time: " + closureTime + ", prototype time: " + prototypeTime + ", static object time: " + staticTime);
}
TestPerformance();
Esta prueba es una modificación del código que encontré en:
Resultados:
IE6: tiempo de cierre: 1062, tiempo de prototipo: 766, tiempo de objeto estático: 406
IE8: tiempo de cierre: 781, tiempo de prototipo: 406, tiempo de objeto estático: 188
FF: tiempo de cierre: 233, tiempo de prototipo: 141, tiempo de objeto estático: 94
Safari: tiempo de cierre: 152, tiempo del prototipo: 12, tiempo del objeto estático: 6
Chrome: tiempo de cierre: 13, tiempo de prototipo: 8, tiempo de objeto estático: 3
La lección aprendida es que, si NO NECESITA crear una instancia de muchos objetos diferentes de la misma clase, entonces crearlo como un objeto estático gana sin problemas. Así que piense cuidadosamente sobre qué clase de clase realmente necesita.
La primera conclusión es que el acceso estático es realmente más lento que el prototipado real. Curiosamente, la Versión 23 de esta prueba tiene un prototipo defectuoso (Variable X), que simplemente devuelve el objeto prototipo completamente reemplazado una y otra vez y cuando estaba creando mi prueba, este prototipo fue aún más lento que mi "prototipo real" prueba.
De todos modos, a la respuesta : a menos que mi prueba sea defectuosa, muestra que el prototipado real es el más rápido. Combina o es al menos igual al objeto estático al ignorar la instanciación. esto: las asignaciones en instanciación y las variables privadas son mucho más lentas. No hubiera supuesto que las variables privadas serían tan lentas.
Puede ser de interés que extienda el prototipo Object con jQuery.extend en el medio y que sea aproximadamente la misma velocidad que la asignación directa. La extensión estaba fuera de la prueba en sí, por supuesto. Al menos esta es una forma de eludir la escritura molesta ".prototype". - Piezas todo el tiempo.