Por que usar expressões de função nomeadas?

92

Temos duas maneiras diferentes de fazer expressão de função em JavaScript:

Expressão de função nomeada (NFE) :

var boo = function boo () {
  alert(1);
};

Expressão de função anônima :

var boo = function () {
  alert(1);
};

E ambos podem ser chamados com boo();. Eu realmente não consigo ver por que / quando devo usar funções anônimas e quando devo usar expressões de função nomeada. Que diferença existe entre eles?

Afshin Mehrabani
fonte

Respostas:

85

No caso da expressão de função anônima, a função é anônima  - literalmente, não tem nome. A variável à qual você está atribuindo tem um nome, mas a função não. (Atualização: isso era verdade através do ES5. A partir do ES2015 [também conhecido como ES6], muitas vezes uma função criada com uma expressão anônima obtém um nome verdadeiro [mas não um identificador automático], continue lendo ...)

Os nomes são úteis. Os nomes podem ser vistos em rastreamentos de pilha, pilhas de chamadas, listas de pontos de interrupção, etc. Os nomes são uma coisa boa ™.

(Você costumava ter que tomar cuidado com as expressões de função nomeada em versões mais antigas do IE [IE8 e abaixo], porque eles criaram por engano dois objetos de função completamente separados em dois momentos completamente diferentes [mais no meu artigo do blog Double take ]. Se você precisar suporte IE8 [!!], provavelmente é melhor ficar com expressões de função anônimas ou declarações de função , mas evite expressões de função nomeadas.)

Uma coisa importante sobre uma expressão de função nomeada é que ela cria um identificador dentro do escopo com esse nome para a função dentro do corpo da função:

var x = function example() {
    console.log(typeof example); // "function"
};
x();
console.log(typeof example);     // "undefined"

No entanto, a partir do ES2015, muitas expressões de função "anônimas" criaram funções com nomes, e isso foi precedido por vários mecanismos JavaScript modernos que eram bastante inteligentes ao inferir nomes a partir do contexto. No ES2015, sua expressão de função anônima resulta em uma função com o nome boo. No entanto, mesmo com a semântica ES2015 +, o identificador automático não é criado:

var obj = {
    x: function() {
       console.log(typeof x);   // "undefined"
       console.log(obj.x.name); // "x"
    },
    y: function y() {
       console.log(typeof y);   // "function"
       console.log(obj.y.name); // "y"
    }
};
obj.x();
obj.y();

A atribuição do nome da função é feita com a operação abstrata SetFunctionName usada em várias operações na especificação.

A versão curta é basicamente qualquer momento em que uma expressão de função anônima aparece no lado direito de algo como uma atribuição ou inicialização, como:

var boo = function() { /*...*/ };

(ou pode ser letou em constvez de var) , ou

var obj = {
    boo: function() { /*...*/ }
};

ou

doSomething({
    boo: function() { /*...*/ }
});

(esses dois últimos são realmente a mesma coisa) , a função resultante terá um nome ( boo, nos exemplos).

Há uma exceção importante e intencional: atribuir a uma propriedade em um objeto existente:

obj.boo = function() { /*...*/ }; // <== Does not get a name

Isso ocorreu devido a preocupações com vazamento de informações quando o novo recurso estava passando pelo processo de adição; detalhes em minha resposta a outra pergunta aqui .

TJ Crowder
fonte
1
É importante notar que existem pelo menos dois lugares em que o uso de NFEs ainda oferece vantagens concretas: em primeiro lugar, para funções destinadas a serem usadas como construtores por meio do newoperador (dar a todos esses nomes de funções torna a .constructorpropriedade mais útil durante a depuração para descobrir o que diabos algum objeto é uma instância de), e para literais de função passados ​​diretamente para uma função sem primeiro serem atribuídos a uma propriedade ou variável (por exemplo setTimeout(function () {/*do stuff*/});). Até mesmo o Chrome mostra esses (anonymous function)nomes , a menos que você ajude nomeando-os.
Mark Amery
4
@MarkAmery: "isso ainda é verdade? Eu ... tentei CTRL-F para essas regras e não consegui encontrar" Oh sim. :-) Está espalhado por toda a especificação ao invés de estar em um lugar definindo um conjunto de regras, apenas procure por "setFunctionName". Eu adicionei um pequeno subconjunto de links acima, mas ele aparece em aproximadamente 29 lugares diferentes atualmente. Eu ficaria apenas um pouco surpreso se seu setTimeoutexemplo não pegasse o nome do argumento formal declarado para setTimeout, se houvesse um. :-) Mas sim, os NFEs são definitivamente úteis se você sabe que não vai lidar com navegadores antigos que os tornam um hash.
TJ Crowder
24

As funções de nomenclatura são úteis se precisarem fazer referência a si mesmas (por exemplo, para chamadas recursivas). Na verdade, se você estiver passando uma expressão de função literal como um argumento diretamente para outra função, essa expressão de função não pode fazer referência a si mesma diretamente no modo estrito ES5, a menos que seja nomeada.

Por exemplo, considere este código:

setTimeout(function sayMoo() {
    alert('MOO');
    setTimeout(sayMoo, 1000);
}, 1000);

Seria impossível escrever esse código de maneira tão limpa se a expressão de função passada para setTimeoutfosse anônima; precisaríamos atribuí-lo a uma variável antes da setTimeoutchamada. Dessa forma, com uma expressão de função nomeada, é ligeiramente mais curto e mais organizado.

Era historicamente possível escrever código como este, mesmo usando uma expressão de função anônima, explorando arguments.callee...

setTimeout(function () {
    alert('MOO');
    setTimeout(arguments.callee, 1000);
}, 1000);

... mas arguments.calleeestá obsoleto e é totalmente proibido no modo estrito ES5. Portanto, o MDN aconselha:

Evite usar arguments.callee()tanto por expressões de função dando um nome ou usar uma declaração de função em que uma função deve chamar-se.

(ênfase minha)

Mark Amery
fonte
3

Se uma função for especificada como Expressão de Função, ela pode receber um nome.

Estará disponível apenas dentro da função (exceto IE8-).

var f = function sayHi(name) {
  alert( sayHi ); // Inside the function you can see the function code
};

alert( sayHi ); // (Error: undefined variable 'sayHi')

Este nome destina-se a uma chamada de função recursiva confiável, mesmo se for gravada em outra variável.

Além disso, o nome NFE (expressão de função nomeada) PODE ser substituído pelo Object.defineProperty(...)método da seguinte forma:

var test = function sayHi(name) {
  Object.defineProperty(test, 'name', { value: 'foo', configurable: true });
  alert( test.name ); // foo
};

test();

Nota: isso não pode ser feito com a Declaração de Função. Este nome de função interna "especial" é especificado apenas na sintaxe de Expressão de Função.

romano
fonte
2

Você deve sempre usar expressões de função nomeadas , por isso:

  1. Você pode usar o nome dessa função quando precisar de recursão.

  2. As funções anônimas não ajudam na depuração, pois você não pode ver o nome da função que causa problemas.

  3. Quando você não nomeia uma função, mais tarde é mais difícil entender o que ela está fazendo. Dar-lhe um nome torna mais fácil de entender.

var foo = function bar() {
    //some code...
};

foo();
bar(); // Error!

Aqui, por exemplo, como a barra de nomes é usada dentro de uma expressão de função, ela não é declarada no escopo externo. Com expressões de função nomeadas, o nome da expressão de função é incluído em seu próprio escopo.

Antero Ukkonen
fonte
1

Usar expressões de função nomeadas é melhor, quando você deseja poder fazer referência à função em questão sem ter que depender de recursos obsoletos, como arguments.callee.

Sudhir Bastakoti
fonte
3
Isso é mais um comentário do que uma resposta. Talvez a elaboração seja benéfica
vsync