Объявление и порядок оценки функции 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'); }
})();
4 ответа:
это не проблема области, и это не проблема закрытия. Проблема заключается в понимании между декларации и выражения.
код JavaScript, поскольку даже первая версия JavaScript Netscape и первая копия Microsoft обрабатываются в два этапа:
Фаза 1: компиляция-на этой фазе код компилируется в синтаксическое дерево (и байт-код или двоичный код в зависимости от движка).
этап 2: выполнение-анализируемый код затем интерпретируется.
синтаксис функции декларация - это:
function name (arguments) {code}
аргументы, конечно, необязательны (код также необязателен, но в чем смысл этого?).
но JavaScript также позволяет создавать функции с помощью выражения. Синтаксис выражений функций аналогичен объявлениям функций, за исключением того, что они написаны в контексте выражения. И выражения являются:
- что-нибудь справа от
=
знак (или:
на объект литералы).- что-нибудь в скобках
()
.- параметры к функциям (это фактически уже покрыто 2).
выражения в отличие от декларации обрабатываются на этапе выполнения, а не на этапе компиляции. И из-за этого порядок выражений имеет значение.
так, чтобы уточните:
// 1 (function() { setTimeout(someFunction, 10); var someFunction = function() { alert('here1'); }; })();
Этап 1: составление. Компилятор видит, что переменная
someFunction
определяется так, что он создает его. По умолчанию все созданные переменные имеют значение undefined. Обратите внимание, что компилятор пока не может назначить значения, потому что значения могут потребоваться интерпретатору для выполнения некоторого кода, чтобы вернуть значение для назначения. И на данном этапе мы еще не выполняем код.Этап 2: Оформление. Переводчик видит, что вы хотите передать переменная
someFunction
to setTimeout. Так оно и есть. К сожалению, текущее значениеsomeFunction
неопределено.
// 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
и создает его. Как и прежде, его значение не определено.Этап 2: Оформление. Интерпретатор передает анонимную функцию setTimeout для последующего выполнения. В этой функции он видит, что вы используете переменную
someFunction
таким образом, он создает закрытие переменной. На данный момент значениеsomeFunction
по-прежнему не определено. Затем он видит, что вы назначаете функциюsomeFunction
. На данный момент значениеsomeFunction
больше не не определено. Через 1/100 секунды срабатывает setTimeout и вызывается функция someFunction. Поскольку его значение больше не является неопределенным, он работает.
случай 4-это действительно другая версия случая 2 с небольшим количеством случая 3. В точке
someFunction
передается в setTimeout он уже существует из-за его объявления.
дополнительные разъяснения:
вы можете задаться вопросом, почему
setTimeout(someFunction, 10)
не создает замыкания между локальная копия someFunction и одна переданная в setTimeout. Ответ на это заключается в том, что аргументы функции в JavaScript всегда, всегда передается по значению, если они являются числами или строками или по ссылке для всего остального. Таким образом, setTimeout фактически не получает переменную someFunction, переданную ей (что означало бы создание закрытия), а только получает объект, на который ссылается someFunction (который в данном случае является функцией). Это наиболее широко используется механизм в JavaScript для взлома замыканий (например, в циклах).
область Javascript основана на функциях,а не строго лексической области. это значит, что
Somefunction1 определяется с самого начала вложенной функции, но ее содержимое не определено до тех пор, пока не назначено.
во втором примере назначение является частью объявления, поэтому оно "перемещается" наверх.
в третьем примере переменная существует, когда определено анонимное внутреннее закрытие, но это не используется до 10 секунд спустя, к тому времени значение было присвоено.
четвертый пример имеет как вторую, так и третью причины для работы
, потому что
someFunction1
еще не был назначен на момент вызоваsetTimeout()
выполняется.someFunction3 может выглядеть как аналогичный случай, но так как вы передаете функцию wrapping
someFunction3()
доsetTimeout()
в этом случае, вызовsomeFunction3()
не оценивается до более позднего времени.
Это звучит как базовые при соблюдении правильной процедуры, чтобы держаться подальше от неприятностей. Объявите переменные и функции перед их использованием и объявите функции следующим образом:
function name (arguments) {code}
не объявляйте их с помощью var. Это просто неаккуратно и приводит к проблемам. Если вы войдете в привычку объявлять все, прежде чем использовать его, большинство ваших проблем исчезнет в большой спешке. При объявлении переменных я бы сразу инициализировал их допустимым значением, чтобы гарантировать, что ни один из них не является неопределенным. Я также склонен включать код, который проверяет допустимые значения глобальных переменных перед их использованием функцией. Это дополнительная защита от ошибок.
технические детали того, как все это работает, похожи на физику того, как работает ручная граната, когда вы играете с ней. Мой простой совет-не играть с ручными гранатами в первую очередь.
некоторые простые объявления в начале кода могут решить большинство из них виды проблем, но некоторая очистка кода все еще может потребоваться.
Дополнительная Информация:
Я провел несколько экспериментов, и кажется, что если вы объявите все свои функции таким образом, как описано здесь, на самом деле не имеет значения, в каком порядке они находятся. Если функция A использует функцию B, функция B не должна быть объявлена перед функцией A.Итак, сначала объявите все ваши функции, затем ваши глобальные переменные, а затем поместите свои другие код последний. Следуйте этим эмпирическим правилам, и вы не ошибетесь. Возможно, даже лучше всего поместить ваши объявления в начало веб-страницы, а другой код-в тело, чтобы обеспечить соблюдение этих правил.