Порядок функций JavaScript: почему это имеет значение?


Исходный Вопрос:

JSHint жалуется, когда мой JavaScript вызывает функцию, которая определена дальше по странице, чем вызов к ней. Однако моя страница предназначена для игры, и никакие функции не вызываются, пока все это не загрузится. Так почему же функции порядка появляются в моем коде?

EDIT: я думаю, что я, возможно, нашел ответ.

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

я стону внутри. Похоже, мне нужно провести еще один день, переупорядочивая шесть тысяч строк кода. Кривая обучения с javascript не крутая вообще, но это очень loooooong.

4 93

4 ответа:

tl; dr если вы ничего не звоните, пока все не загрузится, вы должны быть в порядке.


Edit: для обзора, который также охватывает некоторые объявления ES6 (let,const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

это странное поведение зависит от

  1. как вы определяете функции и
  2. когда вы называете их.

вот несколько примеров.

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

это из-за того, что называется поднимать!

существует два способа определения функций: Function декларация и выражение. Разница раздражает и минутная, так что давайте просто скажем это немного неправильно: если вы пишете это как function name() {}, это декларация, и когда вы пишите var name = function() {} (или an анонимная функция, назначенная возвращению, что-то вроде этого), это функция выражение.

во-первых, давайте посмотрим, как обрабатываются переменные:

var foo = 42;

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

сейчас, как функция декларации обрабатываются:

var foo = 42;
function bar() {}

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

The var заявления "бросает"создание на foo до самого верха, но пока не присваивает ему значение. Объявление функции идет следующим в строке, и, наконец, значение назначенное на foo.

а как насчет этого?

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

только декларация на foo перемещается в верхнюю часть. Назначение происходит только после вызова bar сделано, где оно было прежде чем весь поднимать произошел.

и, наконец, для краткости:

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

теперь насчет функции выражения?

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

как и обычные переменные, сначала foo is объявил в самой высокой точке области, то ему присваивается значение.

давайте посмотрим, почему второй пример вызывает ошибку.

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

как мы уже видели раньше, только создание foo поднимается, назначение происходит там, где оно появилось в "исходном" (un-hoisted) коде. Когда bar называется, это перед foo присваивается значение, поэтому foo === undefined. Теперь в функции-теле bar, это как будто ты делаешь undefined(), которым выдает ошибку.

основная причина, вероятно, в том, что JSLint делает только один проход по файлу, поэтому он не знает вас будет определить такую функцию.

Если вы использовали синтаксис оператора функций

function foo(){ ... }

там на самом деле нет никакой разницы, где вы объявляете функцию (она всегда ведет себя так, как если декларация о начале).

С другой стороны, если ваша функция была установлена как обычная переменная

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

вы должны гарантировать вы не будете вызывать его перед инициализацией (это действительно может быть источником ошибок).


поскольку переупорядочивание тонны кода является сложным и может быть источником ошибок сам по себе, я бы предложил вам поиск обходного пути. Я уверен, что вы можете заранее сообщить JSLint имя глобальных переменных, чтобы он не жаловался на необъявленные вещи.

прокомментируйте начало файла

/*globals foo1 foo2 foo3*/

или вы можете использовать текстовое поле для этого. (Я также подумайте, что вы можете передать это в аргументах внутренней функции jslint, если вы можете вмешаться в нее.)

есть слишком много людей, толкающих произвольные правила о том, как JavaScript должен быть написан. Большинство правил-полная чушь.

функция подъема является особенностью в JavaScript, потому что это хорошая идея.

когда у вас есть внутренняя функция, которая часто является полезностью внутренних функций, добавление ее в начало внешней функции является приемлемым стилем написания кода, но у нее есть недостаток, что вам нужно прочитать детали, чтобы добраться до того, что внешняя функция.

вы должны придерживаться одного принципа во всей вашей кодовой базе либо поставить частные функции первыми или последними в вашем модуле или функции. JSHint хорош для обеспечения согласованности, но вы должны абсолютно настроить.jshintrc в соответствии с вашими потребностями, а не настроить исходный код для других людей дурацкие концепции кодирования.

один стиль кодирования, который вы можете увидеть в дикой природе, вы должны избегать, потому что он не дает вам никаких преимуществ и только возможный рефакторинг боль:

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

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

Это именно то, что функция подъема есть, чтобы избежать. Просто изучите язык и используйте его сильные стороны.

только объявление функции поднимаются не выражение функции (назначение).