Объявление функции JavaScript и порядок оценки

Почему первый из этих примеров не работает, но все остальные?

// 1 - does not work
(function() {
setTimeout(someFunction1, 10);
var someFunction1 = function() { alert('here1'); };
})();

// 2
(function() {
setTimeout(someFunction2, 10);
function someFunction2() { alert('here2'); }
})();

// 3
(function() {
setTimeout(function() { someFunction3(); }, 10);
var someFunction3 = function() { alert('here3'); };
})();

// 4
(function() {
setTimeout(function() { someFunction4(); }, 10);
function someFunction4() { alert('here4'); }
})();

Ответ 1

Это не проблема области и проблема с закрытием. Проблема заключается в понимании выражений и .

JavaScript-код, так как даже первая версия JavaScript первой и второй версий Netscape обрабатывается в два этапа:

Этап 1: компиляция - на этом этапе код компилируется в дерево синтаксиса (и байт-код или двоичный файл в зависимости от движка).

Этап 2: выполнение - анализируемый код затем интерпретируется.

Синтаксис функции Объявление:

function name (arguments) {code}

Аргументы, конечно, необязательны (код также является необязательным, но что это за точка?).

Но JavaScript также позволяет создавать функции с помощью выражений . Синтаксис выражения функций аналогичен объявлениям, за исключением того, что они написаны в контексте выражения. И выражения:

  • Все, что находится справа от знака = (или : на объектных литералах).
  • Все в скобках ().
  • Параметры для функций (на самом деле это уже 2).

Выражения в отличие от объявлений обрабатываются на этапе выполнения, а не на этапе компиляции. И из-за этого имеет смысл порядок выражений.

Итак, чтобы уточнить:


// 1
(function() {
setTimeout(someFunction, 10);
var someFunction = function() { alert('here1'); };
})();

Фаза 1: компиляция. Компилятор видит, что переменная someFunction определена так, что она ее создает. По умолчанию все созданные переменные имеют значение undefined. Обратите внимание, что компилятор еще не может присвоить значения в этот момент, потому что для значений может потребоваться интерпретатор для выполнения некоторого кода для возврата значения для назначения. И на этом этапе мы еще не выполняем код.

Этап 2: выполнение. Интерпретатор видит, что вы хотите передать переменную someFunction в setTimeout. И так оно и есть. К сожалению, текущее значение someFunction равно undefined.


// 2
(function() {
setTimeout(someFunction, 10);
function someFunction() { alert('here2'); }
})();

Фаза 1: компиляция. Компилятор видит, что вы объявляете функцию с именем someFunction и создаете ее.

Этап 2: интерпретатор видит, что вы хотите передать someFunction в setTimeout. И так оно и есть. Текущее значение someFunction - это скомпилированное объявление функции.


// 3
(function() {
setTimeout(function() { someFunction(); }, 10);
var someFunction = function() { alert('here3'); };
})();

Фаза 1: компиляция. Компилятор видит, что вы объявили переменную someFunction и создает ее. Как и раньше, его значение undefined.

Этап 2: выполнение. Интерпретатор передает анонимную функцию, чтобы setTimeout выполнялся позже. В этой функции он видит, что вы используете переменную someFunction, чтобы создать закрытие переменной. В этот момент значение someFunction все еще undefined. Затем он видит, что вы назначаете функцию someFunction. В этот момент значение someFunction больше не undefined. 1/100th секунды спустя вызывается триггеры setTimeout и вызывается функция someFunction. Поскольку его значение больше не undefined, оно работает.


Случай 4 - это действительно другая версия случая 2 с небольшим разбросом 3-х случаев. В точке someFunction передается setTimeout, она уже существует из-за ее объявления.


Дополнительные пояснения:

Вы можете задаться вопросом, почему setTimeout(someFunction, 10) не создает замыкание между локальной копией someFunction и переданной в setTimeout. Ответ на этот вопрос заключается в том, что аргументы функции в JavaScript всегда всегда передаются по значению, если они являются числами или строками или по ссылке для всего остального. Таким образом, setTimeout фактически не получает переданную ему переменную someFunction (что означало бы создание замыкания), а скорее получает только объект, на который ссылается someFunction (что в данном случае является функцией). Это наиболее широко используемый механизм JavaScript для нарушения замыканий (например, в циклах).

Ответ 2

Область Javascript основана на функциях, а не на строго лексическом охвате. это означает, что

  • Somefunction1 определяется с начала закрывающей функции, но содержимое undefined пока не назначено.

  • во втором примере назначение является частью объявления, поэтому оно "перемещается" вверх.

  • в третьем примере переменная существует, когда определено анонимное внутреннее закрытие, но оно не используется до 10 секунд позже, а затем значение было назначено.

  • В четвертом примере есть как вторая, так и третья причины работы

Ответ 3

Потому что someFunction1 еще не назначен в момент выполнения вызова setTimeout().

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

Ответ 4

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

function name (arguments) {code}

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

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

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

Дополнительное примечание:
Я провел несколько экспериментов, и кажется, что если вы объявите все свои функции описанным здесь образом, на самом деле не имеет значения, в каком порядке они находятся. Если функция A использует функцию B, функция B не должна быть объявлена ​​ранее функция A.

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