Ho svolto ricerche sulle prestazioni di JavaScript. Ho imparato che quando si accede a più di una volta, di solito è meglio copiare le variabili di chiusura e i membri della classe in ambito locale per accelerare le cose. Ad esempio:Quando si perfezionano le prestazioni, qual è il modo migliore per chiamare più volte i metodi JavaScript?
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
};
Capisco questo; aggiunge una scorciatoia per l'interprete che cerca il valore per nome. Questo concetto diventa molto chiaro quando si tratta di metodi. All'inizio pensavo che avrebbe funzionato allo stesso modo. Ad esempio:
var obj = {
fn: function() {
// Do something
return this.value;
},
value: 100
};
var objFn = obj.fn
objFn();
// call objFn a bunch of times
Così com'è, questo non funzionerà affatto. L'accesso al metodo come questo lo rimuove dal suo ambito. Quando raggiunge la riga this.value, questo si riferisce all'oggetto window e this.value sarà probabilmente indefinito. Invece di chiamare direttamente objFn e perdere scope, potrei riportare il suo scope in esso con objFn.call (obj) ma questo funziona meglio o peggio dell'originale obj.fn()?
Ho deciso di scrivere una sceneggiatura per testarlo e ho ottenuto risultati molto confusi. Questo script esegue iterazioni su diversi test che effettuano più chiamate in loop nella funzione sopra descritta. Il tempo medio impiegato per ciascun test viene inviato al corpo.
Un oggetto viene creato con molti metodi semplici su di esso. I metodi extra ci sono per determinare se l'interprete deve lavorare molto più difficile per individuare un metodo specifico.
Test 1 chiama semplicemente this.a();
Test 2 crea una variabile locale a = this.a quindi chiama a.call (this);
Test 3 crea una variabile locale utilizzando la funzione di collegamento di YUI per preservare l'ambito. Ho commentato questo. Le chiamate alle funzioni extra create da YUI rendono questo modo più lento.
I test 4, 5 e 6 sono copie di 1, 2, 3 tranne che per z anziché a.
La funzione successiva di YUI viene utilizzata per prevenire errori di script in fuga. I tempi vengono eseguiti con i metodi di test effettivi, quindi il setTimeouts non dovrebbe influenzare i risultati. Ogni funzione è chiamata un totale di 10000000 volte. (Facilmente configurabile se si desidera eseguire test.)
Ecco il mio intero documento XHTML che ho usato per testare.
<!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>
Ho eseguito questo utilizzando Windows 7 in tre diversi browser. Questi risultati sono in millisecondi.
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 ha prodotto risultati su come mi aspettavo. Test 1 e Test 4 sono relativamente vicini, Test 2 e Test 5 sono relativamente vicini, e Test 2 e Test 5 impiegano più tempo del Test 1 e del Test 4 perché esiste una chiamata di funzione extra da elaborare.
Chrome Non capisco affatto, ma è molto più veloce, forse il tweaking delle prestazioni inferiori al millisecondo non è necessario.
Qualcuno ha una buona spiegazione dei risultati? Qual è il modo migliore per chiamare più volte i metodi JavaScript?
è possibile aggiungere questo link nella tua domanda, in modo che altri utenti possano eseguire i test stessi? - http://jsfiddle.net/Lbbx5/ – Anurag
Articoli correlati: [Widget JavaScript senza "this"] (http://michaux.ca/articles/javascript-widgets-without-this) –
FYI, utilizzando Chromium (Linux) sul mio sistema (relativamente lento), dopo un paio di test sembra che i risultati siano gli stessi di Firefox (solo più veloci): le combinazioni 1/4 e 2/5 sono più o meno vicine e 2 e 5 sono più lente di 1 e 4. –