L'utilizzo di funzioni anonime influisce sulle prestazioni?


89

Mi chiedevo, c'è una differenza di prestazioni tra l'utilizzo di funzioni denominate e funzioni anonime in Javascript?

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

vs

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Il primo è più ordinato poiché non ingombra il tuo codice con funzioni usate raramente, ma è importante che tu dichiari quella funzione più volte?


So che non è nella domanda, ma per quanto riguarda la pulizia / leggibilità del codice penso che il "modo giusto" sia da qualche parte nel mezzo. Il "disordine" di funzioni di primo livello usate raramente è fastidioso, ma lo è anche il codice fortemente annidato che dipende molto da funzioni anonime che vengono dichiarate in linea con la loro invocazione (si pensi all'inferno di callback node.js). Sia il primo che il secondo possono rendere difficile il tracciamento del debug / esecuzione.
Zac B

I test delle prestazioni seguenti eseguono la funzione per migliaia di iterazioni. Anche se vedi una differenza sostanziale, la maggior parte dei casi d'uso non lo farà in iterazioni di quell'ordine. Quindi è meglio scegliere quello che si adatta alle tue esigenze e ignorare le prestazioni per questo caso particolare.
utente

@nickf ovviamente è una domanda troppo vecchia, ma guarda la nuova risposta aggiornata
Chandan Pasunoori

Risposte:


89

Il problema delle prestazioni qui è il costo della creazione di un nuovo oggetto funzione ad ogni iterazione del ciclo e non il fatto che si utilizza una funzione anonima:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

Stai creando un migliaio di oggetti funzione distinti anche se hanno lo stesso corpo di codice e nessun legame con l'ambito lessicale ( chiusura ). Quanto segue sembra più veloce, d'altra parte, perché assegna semplicemente lo stesso riferimento di funzione agli elementi dell'array durante il ciclo:

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

Se dovessi creare la funzione anonima prima di entrare nel ciclo, quindi assegnare solo riferimenti ad essa agli elementi dell'array mentre sei all'interno del ciclo, scoprirai che non ci sono prestazioni o differenze semantiche di sorta rispetto alla versione della funzione denominata:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

In breve, non vi è alcun costo di prestazioni osservabile nell'utilizzo di funzioni anonime rispetto a quelle denominate.

Per inciso, può sembrare dall'alto che non ci sia differenza tra:

function myEventHandler() { /* ... */ }

e:

var myEventHandler = function() { /* ... */ }

La prima è una dichiarazione di funzione mentre la seconda è un'assegnazione di variabile a una funzione anonima. Sebbene possano sembrare che abbiano lo stesso effetto, JavaScript li tratta in modo leggermente diverso. Per capire la differenza, consiglio di leggere " Ambiguità della dichiarazione della funzione JavaScript ".

Il tempo di esecuzione effettivo per qualsiasi approccio sarà in gran parte dettato dall'implementazione del compilatore e del runtime da parte del browser. Per un confronto completo delle prestazioni dei browser moderni, visitare il sito JS Perf


Hai dimenticato le parentesi prima del corpo della funzione. L'ho appena provato, sono obbligatori.
Chinoto Vokro

sembra che i risultati del benchmark siano molto dipendenti dal motore js!
aleclofabbro

3
Non c'è un difetto nell'esempio JS Perf: il caso 1 definisce solo la funzione, mentre il caso 2 e 3 sembrano chiamare accidentalmente la funzione.
bluenote10

Quindi, usando questo ragionamento, significa che quando si sviluppano node.jsapplicazioni web, è meglio creare le funzioni al di fuori del flusso di richieste e passarle come callback, piuttosto che creare callback anonimi?
Xavier T Mukodi

23

Ecco il mio codice di prova:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

I risultati:
Test 1: 142 ms Test 2: 1983 ms

Sembra che il motore JS non riconosca che è la stessa funzione in Test2 e la compila ogni volta.


3
In quale browser è stato condotto questo test?
andynil

5
Tempi per me su Chrome 23: (2 ms / 17 ms), IE9: (20 ms / 83 ms), FF 17: (2 ms / 96 ms)
Davy8

La tua risposta merita più peso. I miei tempi su Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Chiaramente la funzione anonima in un ciclo ha prestazioni peggiori.
ThisClark

3
Questo test non è così utile come sembra. In nessuno dei due esempi la funzione interior viene effettivamente eseguita. In effetti, tutto questo test mostra che creare una funzione 10000000 volte è più veloce che creare una funzione una volta.
Nucleon

2

Come principio di progettazione generale, dovresti evitare di impiantare lo stesso codice più volte. Invece dovresti trasformare il codice comune in una funzione ed eseguire quella funzione (generale, ben testata, facile da modificare) da più posti.

Se (a differenza di quello che deduci dalla tua domanda) stai dichiarando la funzione interna una volta e usando quel codice una volta (e non hai nient'altro di identico nel tuo programma) allora una funzione anonima probabilmente (questa è una supposizione gente) viene trattata allo stesso modo dal compilatore come una normale funzione denominata.

È una funzionalità molto utile in casi specifici, ma non dovrebbe essere utilizzata in molte situazioni.


1

Non mi aspetto molta differenza, ma se ce n'è una, probabilmente varierà in base al motore di scripting o al browser.

Se trovi che il codice è più facile da elaborare, le prestazioni non sono un problema a meno che non ti aspetti di chiamare la funzione milioni di volte.


1

Dove possiamo avere un impatto sulle prestazioni è nell'operazione di dichiarazione delle funzioni. Ecco un benchmark di dichiarazione di funzioni all'interno del contesto di un'altra funzione o all'esterno:

http://jsperf.com/function-context-benchmark

In Chrome l'operazione è più veloce se dichiariamo la funzione all'esterno, ma in Firefox è il contrario.

In un altro esempio vediamo che se la funzione interna non è una funzione pura, avrà una mancanza di prestazioni anche in Firefox: http://jsperf.com/function-context-benchmark-3


0

Ciò che sicuramente renderà il tuo loop più veloce su una varietà di browser, in particolare i browser IE, è il loop come segue:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

Hai inserito un 1000 arbitrario nella condizione del ciclo, ma ottieni la mia deriva se vuoi passare attraverso tutti gli elementi nell'array.


0

un riferimento sarà quasi sempre più lento della cosa a cui si riferisce. Pensala in questo modo: supponiamo che tu voglia stampare il risultato dell'aggiunta di 1 + 1. Il che ha più senso:

alert(1 + 1);

o

a = 1;
b = 1;
alert(a + b);

Mi rendo conto che è un modo davvero semplicistico di vederlo, ma è illustrativo, giusto? Utilizza un riferimento solo se verrà utilizzato più volte, ad esempio quale di questi esempi ha più senso:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

o

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

La seconda è una pratica migliore, anche se ha più linee. Si spera che tutto questo sia utile. (e la sintassi jquery non ha buttato fuori nessuno)


0

@nickf

(vorrei avere il rappresentante di commentare, ma ho appena trovato questo sito)

Il punto è che qui c'è confusione tra funzioni denominate / anonime e il caso d'uso dell'esecuzione + della compilazione in un'iterazione. Come ho illustrato, la differenza tra anon + named è di per sé trascurabile: sto dicendo che è il caso d'uso che è difettoso.

Mi sembra ovvio, ma in caso contrario penso che il miglior consiglio sia "non fare cose stupide" (di cui uno è il cambio di blocco costante + la creazione di oggetti di questo caso d'uso) e se non sei sicuro, prova!



0

Gli oggetti anonimi sono più veloci degli oggetti con nome. Ma chiamare più funzioni è più costoso e in una misura che eclissa qualsiasi risparmio che potresti ottenere dall'utilizzo di funzioni anonime. Ogni funzione chiamata si aggiunge allo stack di chiamate, che introduce una piccola ma non banale quantità di overhead.

Ma a meno che tu non stia scrivendo routine di crittografia / decrittografia o qualcosa di altrettanto sensibile alle prestazioni, come molti altri hanno notato è sempre meglio ottimizzare per un codice elegante e di facile lettura su codice veloce.

Supponendo che tu stia scrivendo codice ben progettato, i problemi di velocità dovrebbero essere responsabilità di coloro che scrivono gli interpreti / compilatori.


0

@nickf

Questo è un test piuttosto faticoso, tuttavia, stai confrontando il tempo di esecuzione e di compilazione che ovviamente costerà il metodo 1 (compila N volte, a seconda del motore JS) con il metodo 2 (compila una volta). Non riesco a immaginare uno sviluppatore JS che passerebbe il codice di scrittura di prova in questo modo.

Un approccio molto più realistico è l'assegnazione anonima, poiché in effetti stai usando per il tuo metodo document.onclick è più simile al seguente, che in realtà favorisce leggermente il metodo anon.

Utilizzando un framework di test simile al tuo:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

0

Come sottolineato nei commenti alla risposta di @nickf: la risposta a

Creare una funzione una volta è più veloce che crearla un milione di volte

è semplicemente sì. Ma come mostra la sua performance JS, non è più lento di un fattore di un milione, dimostrando che in realtà diventa più veloce nel tempo.

La domanda più interessante per me è:

Come si confronta una creazione + esecuzione ripetuta per creare una volta + esecuzione ripetuta .

Se una funzione esegue un calcolo complesso, il tempo necessario per creare l'oggetto funzione è molto probabilmente trascurabile. Ma per quanto riguarda il sovraccarico di creare nei casi in cui la corsa è veloce? Per esempio:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

Questo JS Perf mostra che la creazione della funzione solo una volta è più veloce come previsto. Tuttavia, anche con un'operazione molto rapida come una semplice aggiunta, il sovraccarico di creare ripetutamente la funzione è solo una piccola percentuale.

La differenza probabilmente diventa significativa solo nei casi in cui la creazione dell'oggetto funzione è complessa, pur mantenendo un tempo di esecuzione trascurabile, ad esempio se l'intero corpo della funzione è racchiuso in un file if (unlikelyCondition) { ... }.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.