Для управления областями видимости в JavaScript используются функции. Переменная, объявленная внутри функции, является локальной по отношению к этой функции и недоступна за ее пределами. С другой стороны, переменные, объявленные или просто используемые за пределами функций, являются глобальными. В любой среде выполнения JavaScript существует глобальный объект, доступный при использовании ссылки this за пределами функций. Любая создаваемая вами глобальная переменная становится свойством глобального объекта. Для удобства глобальный объект в броузерах имеет дополнительное свойство window, которое (обычно) ссылается на сам глобальный объект.

Следующий фрагмент демонстрирует, как можно создать глобальную переменную и обратиться к ней в браузере:

myglobal = “hello”; // антишаблон 
console.log(myglobal); // “hello” 
console.log(window.myglobal); // “hello” 
console.log(window[“myglobal”]); // “hello” 
console.log(this.myglobal); // “hello”

Проблемы, связанные с глобальными переменными Проблема, связанная с глобальными переменными, порождается тем, что они доступны всему приложению на JavaScript или всей веб-странице. Все эти переменные находятся в одном глобальном пространстве имен, вследствие чего всегда есть вероятность конфликта имен – когда две различные части приложения определяют глобальные переменные с одинаковыми именами, но для разных целей. Кроме того, в веб-страницы часто включается программный код, написанный сторонними разработчиками, например:

  • Сторонние библиотеки JavaScript
  • Сценарии рекламодателя
  • Сторонние сценарии для отслеживания пользователей и сбора статистической информации
  • Различные виджеты, эмблемы и кнопки

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

Самым действенным шаблоном, позволяющим уменьшить количество глобальных переменных, является повсеместное использование объявлений var. Легкость непреднамеренного создания глобальных переменных в JavaScript обусловлена двумя особенностями этого языка. Во-первых, в нем допускается использовать переменные без их объявления. И во-вторых, в этом языке имеется понятие подразумеваемых глобальных переменных, когда любая необъявленная переменная превращается в свой- ство глобального объекта (и становится доступной, как любая другая объявленная глобальная переменная). Взгляните на следующий пример:

function sum(x, y) { // антишаблон: подразумеваемая глобальная переменная 
result = x + y; 
return result; 
}

 В этом фрагменте используется необъявленная переменная result. Эта функция прекрасно работает, но после ее вызова в глобальном пространстве имен появится переменная result, что впоследствии может стать источником ошибок. Чтобы избежать подобных проблем, всегда объявляйте переменные с помощью инструкции var, как показано в улучшенной версии функции sum():

function sum(x, y) { 
var result = x + y; 
return result; 
}

 Еще один антишаблон, применение которого приводит к созданию подразумеваемых глобальных переменных, – это объединение нескольких операций присваивания в одной инструкции var. Следующий фрагмент создаст локальную переменную a, но переменная b будет глобальной, что, вероятно, не совсем совпадает с намерениями программиста:

// антишаблон, не используйте его 
function foo() { 
var a = b = 0; 
// ... 
}

Если вам интересно разобраться, объясню, что это происходит из-за того, что выражения присваивания выполняются справа налево. Сначала выполняется выражение b = 0, а в данном случае b – это необъявленная переменная. Это выражение возвращает значение 0, которое присваивается новой локальной переменной, объявленной инструкции. Другими словами, данная инструкция эквивалентна следующей: var a = (b = 0); Если бы переменные уже были объявлены, то объединение операций присваивания в одну инструкцию не привело бы к созданию глобальной переменной. Например:

function foo() { 
var a, b; 
// ... 
a = b = 0; // обе переменные – локальные 
}

Переносимость – еще одна причина избегать глобальных переменных. Когда необходимо, чтобы программный код выполнялся в различных окружениях, глобальные переменные становятся небезопасными, потому что можно по неосторожности затереть какой-нибудь объект окружения, отсутствующий в среде выполнения, для которой первоначально создавался сценарий (из-за чего вы могли считать имя, выбранное для переменной, безопасным), но имеющийся в каких-то других окружениях.

Побочные эффекты, возникающие в отсутствие объявления var

Между подразумеваемыми глобальными переменными и глобальными переменными, объявленными явно, существует одно тонкое отличие – возможность использования оператора delete для удаления переменных:

  • Глобальные переменные, созданные явно с помощью инструкции var (в программе, за пределами какой-либо функции), не могут быть удалены.
  • Подразумеваемые переменные, созданные без помощи инструкции var (независимо от того, были они созданы в пределах функции или нет), могут быть удалены.

Следующий фрагмент демонстрирует, что подразумеваемые глобальные переменные технически не являются настоящими переменными, но являются свойствами глобального объекта. Свойства могут удаляться с помощью оператора delete, тогда как переменные – нет:

// определение трех глобальных переменных 
var global_var = 1; 
global_novar = 2; 
// антишаблон 
(function () { 
global_fromfunc = 3; // антишаблон 
}());
// попытка удаления 
delete global_var; // false 
delete global_novar; // true 
delete global_fromfunc; // true 
// проверка удаления 
typeof global_var; // “number” 
typeof global_novar; // “undefined” 
typeof global_fromfunc; // “undefined”

В строгом режиме, предусматриваемом стандартом ES5, попытка присвоить значение необъявленной переменной (такой как переменные в двух антишаблонах выше) приведет к появлению ошибки. Доступ к глобальному объекту В броузерах глобальный объект доступен в любой части сценария как свойство с именем window (если только вы не предприняли каких-либо неожиданных действий, например, объявив локальную переменную с именем window). Однако в других окружениях это удобное свойство может называться иначе (или вообще быть недоступным для программиста). Если вам необходимо обратиться к глобальному объекту без использования жестко определенного идентификатора window, можно выполнить следующие операции на любом уровне вложенности в области видимости функции:

var global = (function () { 
return this; 
}()); 

Этот прием позволит получить доступ к глобальному объекту в любом месте, потому что внутри функций, которые вызываются как функ-ции (то есть не как конструкторы с оператором new), ссылка this всегда указывает на глобальный объект. Однако этот прием не может использоваться в строгом режиме, предусматриваемом стандартом ECMAScript 5, поэтому вам необходимо будет задействовать иной шаблон, если потребуется обеспечить работоспособность сценария в строгом режиме. Например, если вы разрабатываете библиотеку, можно обернуть программный код библиотеки немедленно вызываемой функцией (этот шаблон рассматривается в главе 4), а из глобальной области видимости передавать этой функции ссылку this в виде параметра.

Шаблон единственной инструкции var

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

  • Все локальные переменные, необходимые функции, объявляются в одном месте, что упрощает их поиск
  • Предотвращает логические ошибки, когда переменная используется до того, как она будет объявлена
  • Помогает не забывать объявлять переменные и позволяет уменьшить количество глобальных переменных
  • Уменьшает объем программного кода (который придется вводить и передавать по сети)

Шаблон единственной инструкции var выглядит следующим образом:

function func() { 
var 
a = 1, 
b = 2, 
sum = a + b, 
myobject = {}, 
i, 
j;
 
// тело функции... 
}

 Вы используете единственную инструкцию var и объявляете в ней сразу несколько переменных, разделяя их запятыми. При этом допускается одновременно инициализировать переменные начальными значения- ми. Это поможет предотвратить логические ошибки (все неинициализированные переменные по умолчанию получают значение undefined), а также повысит удобочитаемость программного кода. Когда в дальнейшем вы вновь вернетесь к этому коду, вы быстро сможете разобраться, для чего предназначена каждая переменная, опираясь на начальные значения. Например, является та или иная переменная объектом или целым числом. Кроме того, во время объявления переменной можно предусмотреть выполнение фактических операций, таких как вычисление суммы sum = a + b в предыдущем примере.

Другой пример: работая с деревом DOM (Document Object Model – объектная модель документа), можно одновременно с объявлением локальных переменных присваивать им ссылки на элементы, как показано ниже:

function updateElement() { 
var 
el = document.getElementById(“result”), 
style = el.style; 

// Выполнение операций над переменными el и style... 
}

Подъем: проблемы с разбросанными переменными

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

// антишаблон 
myname = “global”; // глобальная переменная 

function func() { 
alert(myname); // “undefined” 
var myname = “local”; 
alert(myname); // “local” 
}
 
func(); 

Вы могли бы предположить, что первый вызов alert() выведет запрос «global», а второй – «local». Это вполне разумное предположение, потому что к моменту первого вызова alert() переменная myname еще не была объявлена, и поэтому функция должна была бы, вероятно, «увидеть» глобальную переменную myname. Но на самом деле все совсем не так. Первый вызов alert() выведет текст «undefined», потому что переменная myname считается объявленной как локальная переменная функции (даже несмотря на то, что объявление находится ниже). Все объявления переменных как бы поднимаются в начало функции. Таким образом, чтобы избежать подобных накладок, лучше помещать объявления всех переменных, которые предполагается использовать, в начало функции.

Предыдущий фрагмент действует так, как если бы он был реализован, как показано ниже:

myname = “global”; // глобальная переменная 

function func() { 
var myname; // то же, что и -> var myname = undefined; 

alert(myname); // “undefined” 
myname = “local”; 
alert(myname); // “local” 
} 

func();