Объявление функции 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)

Так какsomeFunction1 еще не был назначен во время звонкаsetTimeout() выполнен.

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

 jnylen08 окт. 2010 г., 05:13
НоsomeFunction2 еще не был назначен, когда вызовsetTimeout() выполняется либо ...?
 mway08 окт. 2010 г., 05:31
+1 за особые функции. Однако только потому, что этоМожно работа не означает, что это должно быть сделано. Всегда объявляйте, прежде чем использовать.
 jnylen08 окт. 2010 г., 15:44
@mway: в моем случае я организовал свой код в «классе» по разделам: приватные переменные, обработчики событий, приватные функции, затем публичные функции. Мне нужен один из моих обработчиков событий для вызова одной из моих личных функций. Для меня сохранение кода организованным таким образом выигрывает у лексического упорядочения объявлений.
 Chuck08 окт. 2010 г., 05:28
@jnylen: объявление функции с помощьюfunction Ключевое слово не совсем точно соответствует присвоению анонимной функции переменной. Функции объявлены какfunction foo() «поднимаются» в начало текущей области, а присваивание переменных происходит в том месте, где они записаны.

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

function name (arguments) {code}

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

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

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

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

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

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

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

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

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

Четвертый пример имеет как вторые, так и третьи причины для работы

 jnylen08 окт. 2010 г., 05:18
В первом пункте вы имеете в видуsomeFunction1?
 Javier08 окт. 2010 г., 05:28
ткс, исправлено .....
Решение Вопроса

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

Код 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 установить время. И так оно и есть. К сожалению текущая стоимость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 для разрыва замыканий (например, в циклах).

 slebetman28 мар. 2013 г., 07:06
@WillsonMock это правильно
 jnylen08 окт. 2010 г., 15:48
Благодарю. Отредактировал заголовок вопроса и теги, чтобы отразить тот факт, что речь не идет о замыканиях.
 ArtBIT08 окт. 2010 г., 07:20
Этот ответ заставляет меня хотеть голосовать несколько раз за один и тот же ответ. Действительно отличный ответ. Спасибо
 slebetman08 окт. 2010 г., 07:25
 Matt Briggs08 окт. 2010 г., 06:59
Это был действительно отличный ответ.
 Matt Briggs08 окт. 2010 г., 07:06
Вероятно, это недостаток понимания замыканий, но я всегда думал о нем как о доступе к области, а не как к созданию чего-то между одной областью и другой. Я также думал, что это было на уровне области, а не на уровне переменных. Не могли бы вы рассказать об этом поподробнее или указать мне то, о чем я могу прочитать? Опять же, отличный ответ, я бы хотел проголосовать дважды.
 Elisabeth15 янв. 2014 г., 00:03
@ Slebetman: Спасибо за это! У вас есть ссылка, возможно, на документацию движка браузера, для двух фаз? Я пытался получить больше информации о фазе компиляции и о том, что именно происходит на этом этапе, и мне не повезло найти хорошие объяснения. Спасибо!
 wmock28 мар. 2013 г., 00:47
@slebetman для объяснения примера 3, вы упомянули, что анонимная функция в setTimeout создает замыкание для переменной someFunction и что на данный момент someFunction все еще не определена, что имеет смысл. Кажется, что единственная причина, по которой пример 3 не возвращает undefined, связана с функцией setTimeout (задержка в 10 миллисекунд позволяет JavaScript выполнить следующий оператор присваивания someFunction, что делает его определенным), верно?
 slebetman08 окт. 2010 г., 07:24
@Matt: я объяснил это в другом месте (несколько раз) на SO. Некоторые из моих любимых объяснений:stackoverflow.com/questions/3572480/...
 Matt Briggs08 окт. 2010 г., 08:16
@slebetman: В этом есть смысл, спасибо :). Опираясь на вашу аналогию, закрытие будет закрытой статической переменной (в терминологии Java), верно? Я думаю, это то, что я получаю, узнавая о них из таких языков, как javascript и ruby, а не из реального функционального языка.
 slebetman29 мар. 2013 г., 07:50
@ GitaarLAB: Когда я писал свой ответ, я основывал его не на какой-либо документации или спецификации, а на тестовом скрипте, который я тестировал на IE, FF, Chrome и Opera. Ни один из протестированных мной браузеров не выдавал сообщения об ошибке, когда я объявил функцию без идентификатора.
 slebetman08 окт. 2010 г., 08:02
@Matt: Технически, замыкания включают не область видимости, а кадр стека (иначе называемый записью активации). Закрытие - это переменная, общая для стековых кадров. Фрейм стека предназначен для определения того, что представляет собой объект для класса. Другими словами, область действия - это то, что программист воспринимает в структуре кода. Кадр стека - это то, что создается во время выполнения в памяти. Это не совсем так, но достаточно близко. Когда вы думаете о поведении во время выполнения, понимания, основанного на объеме, иногда недостаточно.
 slebetman29 мар. 2013 г., 07:47
@GitaarLAB: Без идентификатора это все еще допустимо, только я верю, что оно скомпилировано как выражение вместо объявления. Ни один браузер на сегодняшний день не выдаст ошибку, если результат выражения отбрасывается (не назначается с помощью оператора =)
 GitaarLAB28 мар. 2013 г., 11:39
Ты говоришь:name and arguments are of course optional в вашей функцииdefinition объяснение. Теперь это сбивает с толку .. MDN, MSDN, ES3, ES5 и все книги, которые я прочитал, противоречат этому утверждению и говорят, что только аргументы являются необязательными в объявлении функции:function Identifier ( FormalParameterList opt ){ FunctionBody } (поэтому идентификатор является обязательным), но FunctionExpression:function Identifier opt ( FormalParameterList opt ){ FunctionBody }   Итак, что мне не хватает?
 slebetman29 мар. 2013 г., 07:52
@GitaarLAB: только что протестировал его на последней версии Chrome, и кажется, что теперь он выдает ошибку, если идентификатор не указан. Так что мой ответ устарел. Я исправлю это.

Ваш ответ на вопрос