Ordem das funções JavaScript: por que isso importa?

106

Questão original:

JSHint reclama quando meu JavaScript chama uma função que é definida mais abaixo na página do que a chamada para ela. No entanto, minha página é para um jogo e nenhuma função é chamada até que tudo seja baixado. Então, por que as funções de pedido aparecem em meu código?

EDIT: Acho que posso ter encontrado a resposta.

http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

Estou gemendo por dentro. Parece que preciso passar OUTRO dia reordenando seis mil linhas de código. A curva de aprendizado com javascript não é íngreme, mas é muuuuuito.

Chris Tolworthy
fonte
+1 pela excelente referência na atualização. E espero que isso o convença de que você realmente não precisa reordenar seu código. :)
awm

Respostas:

294

tl; dr Se você não estiver ligando para nada até que tudo carregue, você deve ficar bem.


Editar: para uma visão geral que também cobre algumas declarações ES6 ( let, const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

Este comportamento estranho depende de

  1. Como você define as funções e
  2. Quando você liga para eles.

Aqui estão alguns exemplos.

bar(); //This won't throw an error
function bar() {}

foo(); //This will throw an error
var foo = function() {}
bar();
function bar() {
    foo(); //This will throw an error
}
var foo = function() {}
bar();
function bar() {
    foo(); //This _won't_ throw an error
}
function foo() {}
function bar() {
    foo(); //no error
}
var foo = function() {}
bar();

Isso ocorre por causa de algo chamado içamento !

Existem duas maneiras de definir funções: declaração de função e expressão de função . A diferença é irritante e minuciosa, então vamos apenas dizer uma coisa um pouco errada: se você está escrevendo como function name() {}, é uma declaração , e quando você escreve como var name = function() {}(ou uma função anônima atribuída a um retorno, coisas assim), é uma expressão de função .

Primeiro, vamos ver como as variáveis ​​são tratadas:

var foo = 42;

//the interpreter turns it into this:
var foo;
foo = 42;

Agora, como as declarações de função são tratadas:

var foo = 42;
function bar() {}

//turns into
var foo; //Insanity! It's now at the top
function bar() {}
foo = 42;

As vardeclarações "jogam" a criação de foopara o topo, mas ainda não atribuem o valor a ela. A declaração da função vem a seguir na linha e, finalmente, um valor é atribuído foo.

E quanto a isso?

bar();
var foo = 42;
function bar() {}
//=>
var foo;
function bar() {}
bar();
foo = 42;

Apenas a declaração de fooé movida para o topo. A atribuição vem somente após a chamada para barser feita, onde estava antes de ocorrer todo o içamento.

E, finalmente, para concisão:

bar();
function bar() {}
//turns to
function bar() {}
bar();

Agora, e quanto às expressões de função ?

var foo = function() {}
foo();
//=>
var foo;
foo = function() {}
foo();

Assim como as variáveis ​​regulares, primeiro fooé declarado no ponto mais alto do escopo e, em seguida, é atribuído um valor.

Vamos ver por que o segundo exemplo gera um erro.

bar();
function bar() {
    foo();
}
var foo = function() {}
//=>
var foo;
function bar() {
    foo();
}
bar();
foo = function() {}

Como vimos antes, apenas a criação de fooé içada, a atribuição vem onde apareceu no código "original" (não içada). Quando baré chamado, é antes de fooser atribuído um valor, então foo === undefined. Agora, no corpo da função de bar, é como se você estivesse fazendo undefined(), o que gera um erro.

Zirak
fonte
Desculpe desenterrar isso, mas sobrecargas como Array.prototype.someMethod = function () {} estão suspensas? Parece que estou recebendo erros se esse tipo de coisa estiver no final do meu script.
Edge,
Como você garante que tudo carregue ? Existe alguma prática comum?
zwcloud
6

A principal razão é provavelmente que JSLint faz apenas uma passagem sobre o arquivo para que ele não sabe que você irá definir tal função.

Se você usou sintaxe de instrução de funções

function foo(){ ... }

Na verdade, não há nenhuma diferença onde você declara a função (ela sempre se comporta como se a declaração estivesse no início).

Por outro lado, se sua função foi definida como uma variável regular

var foo = function() { ... };

Você tem que garantir que não vai chamá-lo antes da inicialização (isso pode realmente ser uma fonte de bugs).


Como reordenar toneladas de código é complicado e pode ser uma fonte de bugs, eu sugiro que você procure uma solução alternativa. Tenho certeza de que você pode informar ao JSLint o nome das variáveis ​​globais de antemão, para que ele não reclame sobre coisas não declaradas.

Coloque um comentário no início do arquivo

/*globals foo1 foo2 foo3*/

Ou você pode usar uma caixa de texto para isso. (Eu também acho que você pode passar isso nos argumentos para a função jslint interna, se puder interferir nisso.)

hugomg
fonte
Obrigado. Então a linha / * globals * / funcionará? Bom - qualquer coisa para fazer o JsHint gostar de mim. Ainda sou novo no JavaScript e obtenho pausas inexplicáveis ​​quando atualizo uma página, mas nenhum bug relatado. Então eu percebi que a solução era seguir todas as regras e então ver se isso ainda acontecia.
Chris Tolworthy
4

Existem muitas pessoas empurrando regras arbitrárias sobre como o JavaScript deve ser escrito. A maioria das regras é um lixo absoluto.

A elevação de função é um recurso em JavaScript porque é uma boa ideia.

Quando você tem uma função interna que muitas vezes é a utilidade de funções internas, adicioná-la ao início da função externa é um estilo aceitável de escrever código, mas tem a desvantagem de ter que ler os detalhes para saber o que a função externa sim.

Você deve seguir um princípio em toda a sua base de código ou colocar as funções privadas primeiro ou por último em seu módulo ou função. JSHint é bom para garantir consistência, mas você deve ajustar ABSOLUTAMENTE o .jshintrc para atender às suas necessidades, NÃO ajustar seu código-fonte aos conceitos de codificação malucos de outras pessoas.

Um estilo de codificação que você pode ver à solta e que deve ser evitado porque ele não oferece vantagens e apenas a dor de refatoração possível:

function bigProcess() {
    var step1,step2;
    step1();
    step2();

    step1 = function() {...};
    step2 = function() {...};
}

Isso é exatamente o que o içamento de função deve ser evitado. Apenas aprenda a língua e explore seus pontos fortes.

Henrik Vendelbo
fonte
2

Apenas a declaração de função é içada, não a expressão de função (atribuição).

Abhay Srivastav
fonte