В JavaScript область видимости — это важная, но неоднозначная концепция. Области видимости, при правильном подходе к их использованию, позволяют применять надёжные шаблоны проектирования, помогают избежать нежелательных побочных эффектов в программах. В этом материале мы проанализируем различные типы областей видимости в JavaScript, поговорим о том, как они работают. Хорошее понимание этого механизма позволит вам улучшить качество кода.
Картинка по запросу «области видимости». Извините, если вызвали приступ ностальгии )
Элементарное определение области видимости выглядит так: это область, где компилятор ищет переменные и функции, когда они ему нужны. Думаете, что звучит это слишком просто? Предлагаем разобраться вместе.
'use strict'
var foo = 'foo';
var wow = 'wow';
function bar (wow) {
var pow = 'pow';
console.log(foo); // 'foo'
console.log(wow); // 'zoom'
}
bar('zoom');
console.log(pow); // ReferenceError: pow is not defined
'use strict'
// Переменные подняты в верхнюю часть текущей области видимости
var foo;
var wow;
// Объявления функций подняты целиком, вместе с присвоением, в верхнюю часть текущей области видимости
function bar (wow) {
var pow;
pow = 'pow';
console.log(foo);
console.log(wow);
}
foo = 'foo';
wow = 'wow';
bar('zoom');
console.log(pow); // ReferenceError: pow is not defined
pow
была объявлена в функции bar
, так как это — её область видимости. Обратите внимание на то, что переменная объявлена не в родительской, по отношению к функции, области видимости.wow
функции bar
так же объявлен в области видимости функции. На самом деле, все параметры функции неявно объявлены в её области видимости, и именно поэтому команда console.log(wow)
в девятой строке, внутри функции, выводит zoom
вместо wow
.eval
или with
. Полагаем, что эти команды, в любом случае, использовать не стоит.bar()
в строке 12.bar
прежде чем выполнить этот вызов, делает он это, начиная с выполнения поиска в текущей области видимости. В тот момент текущей является глобальная область видимости. Благодаря первому проходу, то есть компиляции, мы знаем, что объявление bar
находится в верхней части кода, поэтому интерпретатор может найти его и выполнить функцию.console.log(foo);
, интерпретатору, прежде чем исполнить эту команду, понадобится найти объявление foo
. Первое, что он делает, опять же, ищет в текущей области видимости, которой в этот момент является область видимости функции bar
, а не глобальная область видимости. Объявлена ли переменная foo
в области видимости функции? Нет, это не так. Затем он переходит на уровень вверх, к родительской области видимости, и ищет объявление переменной там. Область видимости, в которой объявлена функция — это глобальная область видимости. Объявлена ли переменная foo в глобальной области видимости? Да, это так. Поэтому интерпретатор может взять значение переменной и исполнить команду.ReferenceError
.foo
, объявленная в текущей области видимости функции, затенит или скроет переменную с тем же именем, объявленную в родительской области видимости. Взглянем на следующий пример для того, чтобы лучше разобраться с этой идеей:'use strict'
var foo = 'foo';
function bar () {
var foo = 'bar';
console.log(foo);
}
bar();
bar
, а не foo
, так как объявление переменной foo
в шестой строке перекроет объявление переменной с таким же именем в третьей строке.'use strict'
function convert (amount) {
var _conversionRate = 2; // Доступно только в функциональной области видимости
return amount * _conversionRate;
}
console.log(convert(5));
console.log(_conversionRate); // ReferenceError: _conversionRate is not defined
catch
в конструкции try / catch
имеет блочную область видимости, что означает, что у этого выражения есть собственная область видимости. Важно отметить, что выражение try
не имеет блочной области видимости, она есть только у выражения catch
. Рассмотрим пример:'use strict'
try {
var foo = 'foo';
console.log(bar);
}
catch (err) {
console.log('In catch block');
console.log(err);
}
console.log(foo);
console.log(err);
bar
, что приведёт к тому, что интерпретатор перейдёт к выражению catch
. В области видимости выражения объявлена переменная err
, которая не будет доступна извне. На самом деле, ошибка будет выдана, когда мы попытаемся вывести в лог значение переменной err
в строке console.log(err)
;. Вот что выведет этот код:In catch block
ReferenceError: bar is not defined
(...Error stack here...)
foo
ReferenceError: err is not defined
(...Error stack here...)
foo
доступна за пределами конструкции try / catch
, а err
— нет.let
и const
переменные и константы неявно присоединяются к текущей блочной области видимости вместо функциональной области видимости. Это означает, что эти конструкции ограничены блоком, в котором они используются, будет ли это блок if
, блок for
, или функция. Вот пример, который поможет лучше это понять:'use strict'
let condition = true;
function bar () {
if (condition) {
var firstName = 'John'; // Доступно во всей функции
let lastName = 'Doe'; // Доступно только в блоке if
const fullName = firstName + ' ' + lastName; // Доступно только в блоке if
}
console.log(firstName); // John
console.log(lastName); // ReferenceError
console.log(fullName); // ReferenceError
}
bar();
let
и const
позволяют нам использовать принцип наименьшего раскрытия (principle of least disclosure). Следование этому принципу означает, что переменная должна быть доступна в наименьшей из возможных областей видимости. До ES6 разработчики часто добивались эффекта блочной области видимости, пользуясь стилистическим приёмом объявления переменных с ключевым словом var
в немедленно исполняемом функциональном выражении (Immediately Invoked Function Expression, IIFE), но теперь, благодаря let
и const
, можно применить функциональный подход. Некоторые из основных преимуществ этого принципа заключаются в избежании нежелательного доступа к переменным, и, таким образом, снижении вероятности ошибок. Кроме того, это позволяет сборщику мусора освобождать память от ненужных переменных при выходе из блочной области видимости.'use strict'
var foo = 'foo';
(function bar () {
console.log('in function bar');
})()
console.log(foo);
in function bar
до вывода foo
, так как функция bar
исполняется немедленно, без необходимости явно вызывать её, используя конструкцию вида bar()
. Это происходит по следующим причинам:'use strict'
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log('index: ' + i);
}, 1000);
}
for
, в котором вызывается асинхронная операция setTimeout
, будет выглядеть так:index: 5
index: 5
index: 5
index: 5
index: 5
for
завершится и счётчик i
окажется равным 5.'use strict'
for (var i = 0; i < 5; i++) {
(function logIndex(index) {
setTimeout(function () {
console.log('index: ' + index);
}, 1000);
})(i)
}
i
в IIFE. У функционального выражения будет собственная область видимости, на которую то, что происходит в цикле for
, уже не подействует. Вот что выведет этот код:index: 0
index: 1
index: 2
index: 3
index: 4
К сожалению, не доступен сервер mySQL