10 KiB
Глобальный объект
Механизм работы функций и переменных в JavaScript очень отличается от большинства языков.
Чтобы его понять, мы в этой главе рассмотрим переменные и функции в глобальной области. А в следующей -- пойдём дальше.
[cut]
Глобальный объект
Глобальными называют переменные и функции, которые не находятся внутри какой-то функции. То есть, иными словами, если переменная или функция не находятся внутри конструкции function
, то они -- "глобальные".
В JavaScript все глобальные переменные и функции являются свойствами специального объекта, который называется "глобальный объект" (global object
).
В браузере этот объект явно доступен под именем window
. Объект window
одновременно является глобальным объектом и содержит ряд свойств и методов для работы с окном браузера, но нас здесь интересует только его роль как глобального объекта.
В других окружениях, например Node.JS, глобальный объект может быть недоступен в явном виде, но суть происходящего от этого не изменяется, поэтому далее для обозначения глобального объекта мы будем использовать "window"
.
Присваивая или читая глобальную переменную, мы, фактически, работаем со свойствами window
.
Например:
//+ run untrusted refresh
var a = 5; // объявление var создаёт свойство window.a
alert(window.a); // 5
Создать переменную можно и явным присваиванием в window
:
//+ run untrusted refresh
window.a = 5;
alert(a); // 5
Порядок инициализации
Выполнение скрипта происходит в две фазы:
- На первой фазе происходит инициализация, подготовка к запуску.
Во время инициализации скрипт сканируется на предмет объявления функций вида Function Declaration, а затем -- на предмет объявления переменных
var
. Каждое такое объявление добавляется вwindow
.Функции, объявленные как Function Declaration, создаются сразу работающими, а переменные -- равными
undefined
. - На второй фазе -- собственно, выполнение.
Присваивание (
=
) значений переменных происходит на второй фазе, когда поток выполнения доходит до соответствующей строчки кода.
В начале кода ниже указано содержание глобального объекта на момент окончания инициализации:
// По окончании инициализации, до выполнения кода:
*!*
// window = { f: function, a: undefined, g: undefined }
*/!*
var a = 5; // при инициализации даёт: window.a=undefined
function f(arg) { /*...*/ } // при инициализации даёт: window.f = function
var g = function(arg) { /*...*/ }; // при инициализации даёт: window.g = undefined
Кстати, тот факт, что к началу выполнения кода переменные и функции уже содержатся в window
, можно легко проверить:
//+ run untrusted refresh
alert("a" in window); // *!*true*/!*, т.к. есть свойство window.a
alert(a); // равно *!*undefined*/!*, присваивание будет выполнено далее
alert(f); // *!*function ...*/!*, готовая к выполнению функция
alert(g); // *!*undefined*/!*, т.к. это переменная, а не Function Declaration
var a = 5;
function f() { /*...*/ }
var g = function() { /*...*/ };
[smart header="Присвоение переменной без объявления"]
В старом стандарте JavaScript переменную можно было создать и без объявления var
:
//+ run
a = 5;
alert(a); // 5
Такое присвоение, как и var a = 5
, создает свойство window.a = 5
. Отличие от var a = 5
-- в том, что переменная будет создана не на этапе входа в область видимости, а в момент присвоения.
Сравните два кода ниже.
Первый выведет undefined
, так как переменная была добавлена в window
на фазе инициализации:
//+ run untrusted refresh
*!*
alert(a); // undefined
*/!*
var a = 5;
Второй код выведет ошибку, так как переменной ещё не существует:
//+ run untrusted refresh
*!*
alert(a); // error, a is not defined
*/!*
a = 5;
Вообще, рекомендуется всегда объявлять переменные через var
.
В современном стандарте присваивание без var
вызовет ошибку:
//+ run
'use strict';
a = 5; // error, a is not defined
[/smart]
[smart header="Конструкции for, if...
не влияют на видимость переменных"]
Фигурные скобки, которые используются в for, while, if
, в отличие от объявлений функции, имеют "декоративный" характер.
В JavaScript нет разницы между объявлением вне блока:
*!*var*/!* i;
{
i = 5;
}
...И внутри него:
i = 5;
{
*!*var*/!* i;
}
Также нет разницы между объявлением в цикле и вне его:
//+ run untrusted refresh
for (*!*var*/!* i=0; i<5; i++) { }
Идентичный по функциональности код:
//+ run untrusted refresh
*!*var i;*/!*
for (i=0; i<5; i++) { }
В обоих случаях переменная будет создана до выполнения цикла, на стадии инициализации, и ее значение будет сохранено после окончания цикла.
[/smart]
[smart header="Не важно, где и сколько раз объявлена переменная"]
Объявлений var
может быть сколько угодно:
var i = 10;
for (var i=0; i<20; i++) {
...
}
var i = 5;
Все var
будут обработаны один раз, на фазе инициализации.
На фазе исполнения объявления var
будут проигнорированы: они уже были обработаны. Зато будут выполнены присваивания.
[/smart]
[warn header="Ошибки при работе с window
в IE8-"]
В старых IE есть две забавные ошибки при работе с переменными в window
:
- Переопределение переменной, у которой такое же имя, как и `id` элемента, приведет к ошибке:
<!--+ run --> <div id="a">...</div> <script> a = 5; // ошибка в IE<9! Правильно будет "var a = 5" alert(a); // никогда не сработает </script>
А если сделать через
var
, то всё будет хорошо.Это была реклама того, что надо везде ставить
var
. - Ошибка при рекурсии через функцию-свойство `window`. Следующий код "умрет" в IE<9:
<!--+ run height=0 --> <script> // рекурсия через функцию, явно записанную в window window.recurse = function(times) { if (times !== 0) recurse(times-1); } recurse(13); </script>
Проблема здесь возникает из-за того, что функция напрямую присвоена в
window.recurse = ...
. Ее не будет при обычном объявлении функции.Этот пример выдаст ошибку только в настоящем IE8! Не IE9 в режиме эмуляции. Вообще, режим эмуляции позволяет отлавливать где-то 95% несовместимостей и проблем, а для оставшихся 5% вам нужен будет настоящий IE8 в виртуальной машине.
Итого
В результате инициализации, к началу выполнения кода:
- Функции, объявленные как `Function Declaration`, создаются полностью и готовы к использованию.
- Переменные объявлены, но равны `undefined`. Присваивания выполнятся позже, когда выполнение дойдет до них.