O uso de funções anônimas afeta o desempenho?

89

Eu estive pensando, existe uma diferença de desempenho entre o uso de funções nomeadas e funções anônimas em 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;
}

O primeiro é mais organizado, pois não confunde seu código com funções raramente usadas, mas faz diferença se você está declarando novamente essa função várias vezes?

nickf
fonte
Eu sei que não está em questão, mas com relação à limpeza / legibilidade do código, acho que o 'jeito certo' está em algum lugar no meio. A "desordem" de funções de nível superior raramente usadas é irritante, mas também o é o código fortemente aninhado que depende muito de funções anônimas que são declaradas em linha com sua invocação (pense no inferno de callback de node.js). Tanto o primeiro quanto o último podem dificultar a depuração / rastreamento de execução.
Zac B de
Os testes de desempenho abaixo executam a função por milhares de iterações. Mesmo que você veja uma diferença substancial, a maioria dos casos de uso não fará isso em iterações dessa ordem. Portanto, é melhor escolher o que atende às suas necessidades e ignorar o desempenho neste caso específico.
usuário
@nickf, é claro, é uma pergunta muito antiga, mas veja a nova resposta atualizada
Chandan Pasunoori

Respostas:

89

O problema de desempenho aqui é o custo de criar um novo objeto de função a cada iteração do loop e não o fato de você usar uma função anônima:

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

Você está criando milhares de objetos de função distintos, embora eles tenham o mesmo corpo de código e nenhuma vinculação ao escopo léxico ( encerramento ). O seguinte parece mais rápido, por outro lado, porque simplesmente atribui a mesma referência de função aos elementos da matriz em todo o loop:

function myEventHandler() {
    // do something
}

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

Se você criasse a função anônima antes de entrar no loop, apenas atribua referências a ela aos elementos da matriz enquanto estiver dentro do loop, você descobrirá que não há nenhuma diferença de desempenho ou semântica em comparação com a versão da função nomeada:

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

Resumindo, não há custo de desempenho observável no uso de funções anônimas em vez de nomeadas.

Como um aparte, pode parecer de cima que não há diferença entre:

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

e:

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

O primeiro é uma declaração de função, enquanto o último é uma atribuição de variável a uma função anônima. Embora possam parecer ter o mesmo efeito, o JavaScript os trata de maneira um pouco diferente. Para entender a diferença, recomendo a leitura, “ Ambigüidade da declaração da função JavaScript ”.

O tempo de execução real para qualquer abordagem será amplamente ditado pela implementação do navegador do compilador e do tempo de execução. Para uma comparação completa do desempenho do navegador moderno, visite o site JS Perf

Atif Aziz
fonte
Você esqueceu os parênteses antes do corpo da função. Acabei de testar, eles são obrigatórios.
Chinoto Vokro de
parece que os resultados do benchmark são muito dependentes do js-engine!
aleclofabbro
3
Não há uma falha no exemplo JS Perf: o caso 1 apenas define a função, enquanto os casos 2 e 3 parecem chamar acidentalmente a função.
bluenote10
Então, usando esse raciocínio, isso significa que, ao desenvolver node.jsaplicativos da Web, é melhor criar as funções fora do fluxo de solicitação e passá-las como callbacks do que criar callbacks anônimos?
Xavier T Mukodi
23

Este é meu código de teste:

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));
}

Os resultados:
Teste 1: 142ms Teste 2: 1983ms

Parece que o mecanismo JS não reconhece que é a mesma função em Test2 e a compila todas as vezes.

nickf
fonte
3
Em qual navegador este teste foi realizado?
andynil
5
Tempos para mim no Chrome 23: (2ms / 17ms), IE9: (20ms / 83ms), FF 17: (2ms / 96ms)
Davy8
Sua resposta merece mais peso. Meus tempos no Intel i5 4570S: Chrome 41 (1/9), IE11 (1/25), FF36 (1/14). Claramente, a função anônima em um loop tem desempenho pior.
ThisClark
3
Este teste não é tão útil quanto parece. Em nenhum dos exemplos a função interna está realmente sendo executada. Efetivamente, tudo o que este teste está mostrando é que criar uma função 10000000 vezes é mais rápido do que criar uma função uma vez.
Nucleon de
2

Como princípio geral de design, você deve evitar implementar o mesmo código várias vezes. Em vez disso, você deve transformar o código comum em uma função e executar essa função (geral, bem testada, fácil de modificar) de vários lugares.

Se (ao contrário do que você infere de sua pergunta) você está declarando a função interna uma vez e usando esse código uma vez (e não tem mais nada idêntico em seu programa), então uma função anônima provavelmente (isso é um palpite, pessoal) é tratada da mesma maneira pelo compilador como uma função nomeada normal.

É um recurso muito útil em casos específicos, mas não deve ser usado em muitas situações.

Tom Leys
fonte
1

Eu não esperaria muita diferença, mas se houver uma, provavelmente irá variar de acordo com o mecanismo de script ou navegador.

Se você achar o código mais fácil de entender, o desempenho não é um problema, a menos que você espere chamar a função milhões de vezes.

Joe Skora
fonte
1

Onde podemos ter um impacto no desempenho é na operação de declarar funções. Aqui está um benchmark de declarar funções dentro do contexto de outra função ou fora:

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

No Chrome a operação é mais rápida se declararmos a função externamente, mas no Firefox é o contrário.

Em outro exemplo, vemos que se a função interna não for uma função pura, ela terá uma falta de desempenho também no Firefox: http://jsperf.com/function-context-benchmark-3

Pablo Estornut
fonte
0

O que definitivamente tornará seu loop mais rápido em uma variedade de navegadores, especialmente navegadores do IE, é o seguinte:

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

Você colocou um 1000 arbitrário na condição de loop, mas entendeu minha sugestão se quisesse passar por todos os itens do array.

Sarhanis
fonte
0

uma referência quase sempre será mais lenta do que aquilo a que se refere. Pense desta forma - digamos que você deseja imprimir o resultado da adição de 1 + 1. O que faz mais sentido:

alert(1 + 1);

ou

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

Percebo que é uma forma muito simplista de ver as coisas, mas é ilustrativo, certo? Use uma referência apenas se ela for ser usada várias vezes - por exemplo, qual destes exemplos faz mais sentido:

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

ou

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

O segundo é uma prática melhor, mesmo que tenha mais linhas. Espero que tudo isso seja útil. (e a sintaxe jquery não confundiu ninguém)

Matt Lohkamp
fonte
0

@nickf

(gostaria de ter o representante para apenas comentar, mas acabei de encontrar este site)

Meu ponto é que há confusão aqui entre funções nomeadas / anônimas e o caso de uso de execução + compilação em uma iteração. Como ilustrei, a diferença entre anon + named é insignificante em si - estou dizendo que é o caso de uso que está com defeito.

Parece óbvio para mim, mas se não, acho que o melhor conselho é "não faça coisas idiotas" (das quais o deslocamento de bloco constante + criação de objeto deste caso de uso é um) e se você não tiver certeza, teste!

Annakata
fonte
0

SIM! As funções anônimas são mais rápidas do que as funções regulares. Talvez, se a velocidade for de extrema importância ... mais importante do que a reutilização de código, considere o uso de funções anônimas.

Há um artigo muito bom sobre a otimização de funções javascript e anônimas aqui:

http://dev.opera.com/articles/view/efficient-javascript/?page=2

Christopher Tokar
fonte
0

Objetos anônimos são mais rápidos do que objetos nomeados. Mas chamar mais funções é mais caro e em um grau que eclipsa qualquer economia que você possa obter com o uso de funções anônimas. Cada função chamada adiciona à pilha de chamadas, o que introduz uma pequena, mas não trivial, quantidade de sobrecarga.

Mas, a menos que você esteja escrevendo rotinas de criptografia / descriptografia ou algo similarmente sensível ao desempenho, como muitos outros notaram, é sempre melhor otimizar para um código elegante e fácil de ler em vez de um código rápido.

Supondo que você esteja escrevendo um código bem arquitetado, as questões de velocidade devem ser responsabilidade de quem está escrevendo os interpretadores / compiladores.

pcorcoran
fonte
0

@nickf

Este é um teste um tanto estúpido, você está comparando o tempo de execução e compilação lá, que obviamente custará o método 1 (compila N vezes, dependendo do mecanismo JS) com o método 2 (compila uma vez). Não consigo imaginar um desenvolvedor JS que passaria pelo período probatório escrevendo código dessa maneira.

Uma abordagem muito mais realista é a atribuição anônima, já que, na verdade, você está usando para o seu documento. O método onclick é mais parecido com o seguinte, que na verdade favorece levemente o método anon.

Usando uma estrutura de teste semelhante à sua:


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");
}
Annakata
fonte
0

Conforme apontado nos comentários para @nickf answer: A resposta para

É criar uma função uma vez mais rápido do que criá-la um milhão de vezes

é simplesmente sim. Mas, como mostra seu desempenho em JS, não é mais lento por um milhão, mostrando que na verdade fica mais rápido com o tempo.

A questão mais interessante para mim é:

Como uma criação + execução repetida se compara à criação uma vez + execução repetida .

Se uma função executa um cálculo complexo, o tempo para criar o objeto de função é provavelmente insignificante. Mas e quanto à sobrecarga de criação nos casos em que a execução é rápida? Por exemplo:

// 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);
}

Este JS Perf mostra que criar a função apenas uma vez é mais rápido como o esperado. No entanto, mesmo com uma operação muito rápida como um simples acréscimo, a sobrecarga de criar a função repetidamente é de apenas alguns por cento.

A diferença provavelmente só se torna significativa nos casos em que a criação do objeto de função é complexa, enquanto mantém um tempo de execução insignificante, por exemplo, se todo o corpo da função é agrupado em um if (unlikelyCondition) { ... }.

bluenote10
fonte