Объявление функции JavaScript и порядок оценки - PullRequest
78 голосов
/ 08 октября 2010

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

// 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 ]

178 голосов
/ 08 октября 2010

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

кода JavaScript, поскольку даже первая версия JavaScript Netscape и его первая копия от Microsoft обрабатываются в два этапа:

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

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

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

function name (arguments) {code}

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

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

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

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

Итак, для уточнения:


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

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

Этап 2: выполнение.Интерпретатор видит, что вы хотите передать переменную someFunction в 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 для разрыва замыканий (например, в циклах).

2 голосов
/ 08 октября 2010

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

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

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

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

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

1 голос
/ 25 июля 2012

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

function name (arguments) {code}

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

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

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

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

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

1 голос
/ 08 октября 2010

Поскольку someFunction1 еще не было назначено во время вызова setTimeout().

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

...