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

106

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

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

РЕДАКТИРОВАТЬ: Думаю, я нашел ответ.

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

Я внутри стону. Похоже, мне нужно провести ДРУГОЙ день, переупорядочивая шесть тысяч строк кода. Кривая обучения с javascript совсем не крутая, но очень длинная.

Крис Толуорти
источник
+1 за отличную ссылку в обновлении. И я надеюсь, что это убедит вас в том, что вам действительно не нужно переупорядочивать код. :)
awm

Ответы:

294

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


Изменить: для обзора, который также охватывает некоторые объявления 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 name() {}, это заявление , и , когда вы пишете , как var name = function() {}(или анонимную функцию , назначенную к возвращению, и тому подобное), это выражение функции .

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

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;

В varведомости «кидает» на создание в fooдо самого верха, но не присваивает значение для нее еще. Далее следует объявление функции, и, наконец, ей присваивается значение foo.

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

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

В начало перемещается только объявление of foo. Присваивание происходит только после вызова bar, где оно было до того, как все подъемы произошли.

И напоследок для краткости:

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

А что насчет функциональных выражений ?

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

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

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

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

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

Зирак
источник
Извините, что откопал это, но есть ли перегрузки вроде Array.prototype.someMethod = function () {}? Кажется, я получаю ошибки, если такие вещи находятся в конце моего сценария.
Edge
Как убедиться, что все загружено ? Есть ли обычная практика?
zwcloud
6

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

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

function foo(){ ... }

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

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

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

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


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

Оставьте комментарий в начале файла

/*globals foo1 foo2 foo3*/

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

hugomg
источник
Спасибо. Значит, строка / * globals * / будет работать? Хорошо - все, чтобы понравиться JsHint. Я все еще плохо знаком с JavaScript и получаю необъяснимые паузы при обновлении страницы, но об ошибках не сообщалось. Так что я решил, что выходом было сыграть по всем правилам, а потом посмотреть, происходит ли это по-прежнему.
Крис Толуорти
4

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

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

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

Вы должны придерживаться одного принципа во всей кодовой базе: помещайте частные функции первыми или последними в модуле или функции. JSHint хорош для обеспечения согласованности, но вы должны АБСОЛЮТНО настроить .jshintrc в соответствии со своими потребностями, а НЕ подгонять исходный код под дурацкие концепции кодирования других людей.

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

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

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

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

Хенрик Вендельбо
источник
2

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

Абхай Шривастав
источник