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

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

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

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

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

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


person Chris Tolworthy    schedule 30.09.2011    source источник
comment
+1 за отличную ссылку в обновлении. И я надеюсь, что это убедит вас в том, что вам действительно не нужно переупорядочивать код. :)   -  person awm    schedule 30.09.2011


Ответы (4)


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() {} (или анонимную функцию, назначенную для return и тому подобное), это функция выражение.

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

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;

Только объявление 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(), что вызывает ошибку.

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

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

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

function foo(){ ... }

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

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

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

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


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

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

/*globals foo1 foo2 foo3*/

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

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

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

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

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

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

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

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

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

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

person Henrik Vendelbo    schedule 18.04.2015

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

person Abhay Srivastav    schedule 13.12.2017