He estado investigando el rendimiento de JavaScript. Aprendí que al acceder más de una vez, generalmente es mejor copiar las variables de cierre y los miembros de la clase en el alcance local para acelerar las cosas. Por ejemplo:Al ajustar el rendimiento, ¿cuál es la mejor manera de llamar a los métodos de JavaScript varias veces?
var i = 100;
var doSomething = function() {
var localI = i;
// do something with localI a bunch of times
var obj = {
a: 100
};
var objA = obj.a;
// do something with objA a bunch of times
};
Entiendo esto; Agrega un atajo para que el intérprete busque el valor por su nombre. Este concepto se vuelve muy poco claro cuando se trata de métodos. Al principio, pensé que funcionaría de la misma manera. Por ejemplo:
var obj = {
fn: function() {
// Do something
return this.value;
},
value: 100
};
var objFn = obj.fn
objFn();
// call objFn a bunch of times
Como está, esto no funcionará en absoluto. Acceder al método de esta manera lo quita de su alcance. Cuando alcanza la línea this.value, esto se refiere al objeto window y this.value probablemente no estará definido. En lugar de llamar directamente a objFn y perder alcance, podría volver a pasar su alcance dentro de él con objFn.call (obj), pero ¿funciona mejor o peor que el obj.fn original()?
Decidí escribir un script para probar esto y obtuve resultados muy confusos. Este script realiza iteraciones sobre varias pruebas que repiten varias veces las llamadas a la función anterior. El tiempo promedio tomado para cada prueba se envía al cuerpo.
Un objeto se crea con muchos métodos simples en él. Los métodos adicionales están ahí para determinar si el intérprete tiene que trabajar mucho más para encontrar un método específico.
La Prueba 1 simplemente llama this.a();
La prueba 2 crea una variable local a = this.a luego llama a.call (this);
La prueba 3 crea una variable local usando la función de vinculación de YUI para preservar el alcance. Comenté esto. Las llamadas a funciones adicionales creadas por YUI hacen que esto sea más lento.
Las pruebas 4, 5 y 6 son copias de 1, 2, 3, excepto que se usa z en lugar de a.
La función posterior de YUI se usa para evitar errores de script fuera de control. La sincronización se realiza en los métodos de prueba reales, por lo que setTimeouts no debe afectar los resultados. Cada función se llama un total de 10000000 veces. (Fácilmente configurable si desea ejecutar pruebas.)
Aquí está mi documento completo XHTML que solía probar.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xml:lang="en" dir="ltr">
<head>
<script type="text/javascript" src="http://yui.yahooapis.com/combo?3.1.2/build/yui/yui-min.js"></script>
<script>
YUI().use('node', function (Y) {
var o = {
value: '',
a: function() {
this.value += 'a';
},
b: function() {
this.value += 'b';
},
c: function() {
this.value += 'c';
},
d: function() {
this.value += 'd';
},
e: function() {
this.value += 'e';
},
f: function() {
this.value += 'f';
},
g: function() {
this.value += 'g';
},
h: function() {
this.value += 'h';
},
i: function() {
this.value += 'i';
},
j: function() {
this.value += 'j';
},
k: function() {
this.value += 'k';
},
l: function() {
this.value += 'l';
},
m: function() {
this.value += 'm';
},
n: function() {
this.value += 'n';
},
o: function() {
this.value += 'o';
},
p: function() {
this.value += 'p';
},
q: function() {
this.value += 'q';
},
r: function() {
this.value += 'r';
},
s: function() {
this.value += 's';
},
t: function() {
this.value += 't';
},
u: function() {
this.value += 'u';
},
v: function() {
this.value += 'v';
},
w: function() {
this.value += 'w';
},
x: function() {
this.value += 'x';
},
y: function() {
this.value += 'y';
},
z: function() {
this.value += 'z';
},
reset: function() {
this.value = '';
},
test1: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.a();
}
return new Date().getTime() - time;
},
test2: function (length) {
var a = this.a,
time = new Date().getTime();
while ((length -= 1)) {
a.call(this);
}
return new Date().getTime() - time;
},
test3: function (length) {
var a = Y.bind(this.a, this),
time = new Date().getTime();
while ((length -= 1)) {
a();
}
return new Date().getTime() - time;
},
test4: function (length) {
var time = new Date().getTime();
while ((length -= 1)) {
this.z();
}
return new Date().getTime() - time;
},
test5: function (length) {
var z = this.z,
time = new Date().getTime();
while ((length -= 1)) {
z.call(this);
}
return new Date().getTime() - time;
},
test6: function (length) {
var z = Y.bind(this.z, this),
time = new Date().getTime();
while ((length -= 1)) {
z();
}
return new Date().getTime() - time;
}
},
iterations = 100, iteration = iterations, length = 100000,
t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, body = Y.one('body');
body.set('innerHTML', '<span>Running ' + iterations + ' Iterations…</span>');
while ((iteration -= 1)) {
Y.later(1, null, function (iteration) {
Y.later(1, null, function() {
o.reset();
t1 += o.test1(length);
});
Y.later(1, null, function() {
o.reset();
t2 += o.test2(length);
});
/*Y.later(1, null, function() {
o.reset();
t3 += o.test3(length);
});*/
Y.later(1, null, function() {
o.reset();
t4 += o.test4(length);
});
Y.later(1, null, function() {
o.reset();
t5 += o.test5(length);
});
/*Y.later(1, null, function() {
o.reset();
t6 += o.test6(length);
});*/
if (iteration === 1) {
Y.later(10, null, function() {
t1 /= iterations;
t2 /= iterations;
//t3 /= iterations;
t4 /= iterations;
t5 /= iterations;
//t6 /= iterations;
//body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 3: a();</dt><dd>' + t3 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd><dt>Test 6: z();</dt><dd>' + t6 + '</dd></dl>');
body.set('innerHTML', '<dl><dt>Test 1: this.a();</dt><dd>' + t1 + '</dd><dt>Test 2: a.call(this);</dt><dd>' + t2 + '</dd><dt>Test 4: this.z();</dt><dd>' + t4 + '</dd><dt>Test 5: z.call(this);</dt><dd>' + t5 + '</dd></dl>');
});
}
}, iteration);
}
});
</script>
</head>
<body>
</body>
</html>
He ejecutado esto con Windows 7 en tres navegadores diferentes. Estos resultados están en milisegundos.
Firefox 3.6.8
Test 1: this.a();
9.23
Test 2: a.call(this);
9.67
Test 4: this.z();
9.2
Test 5: z.call(this);
9.61
Chrome 7.0.503.0
Test 1: this.a();
5.25
Test 2: a.call(this);
4.66
Test 4: this.z();
3.71
Test 5: z.call(this);
4.15
Internet Explorer 8
Test 1: this.a();
168.2
Test 2: a.call(this);
197.94
Test 4: this.z();
169.6
Test 5: z.call(this);
199.02
Firefox e Internet Explorer produjo resultados acerca de la forma en que esperaba. La Prueba 1 y la Prueba 4 están relativamente cerca, la Prueba 2 y la Prueba 5 están relativamente cerca, y la Prueba 2 y la Prueba 5 tardan más tiempo que la Prueba 1 y la Prueba 4 porque hay una llamada de función adicional para procesar.
Chrome No lo entiendo para nada, pero es mucho más rápido, quizás no sea necesario ajustar el rendimiento en menos de milisegundos.
¿Alguien tiene una buena explicación de los resultados? ¿Cuál es la mejor manera de llamar a los métodos de JavaScript varias veces?
se puede añadir este enlace en su pregunta, por lo que otros usuarios pueden ejecutar las mismas pruebas? - http://jsfiddle.net/Lbbx5/ – Anurag
Related read: [JavaScript Widgets sin "this"] (http://michaux.ca/articles/javascript-widgets-without-this) –
FYI, usando Chromium (Linux) en mi sistema (relativamente lento), después de un par de pruebas parece que los resultados son casi los mismos que en Firefox (solo que más rápido): las combinaciones 1/4 y 2/5 son más o menos cercanas y 2 y 5 son más lentas que 1 y 4. –