JavaScript - Область видимости и контекст выполнения
В этой статье познакомимся с таким понятием как область видимости переменных и функций. Рассмотрим доступ к каким переменным и функциям мы имеем из разных мест кода.
Область видимости (scope). Цепочка областей видимости
Область видимости определяет доступность (видимость) переменных и функций вида Function Declaration.
Области видимости создаются во время выполнения JavaScript программы (сценария).
В языке JavaScript (до ES6) выделяют 2 области видимости:
- глобальная (переменная или функция, созданная в этой области видимости, может быть доступна из любой точки программы);
- локальная или функциональная (переменная или функция, созданная в этой области видимости, может быть доступна только внутри неё).
Локальная или функциональная область видимости создаётся во время вызова функции. При этом локальная область у каждого вызова функции, даже одной и той же, будет своя.
// global scope (глобальная область видимости) function salute(welcomeText) { console.log(welcomeText); } salute('Привет'); // вызов функции salute salute('Здравствуйте'); // вызов функции salute

[[Scope]] - это скрытое внутреннее свойство функции, которое она получает во время вызова. Данное свойство содержит ссылку на ту область видимости, в которой данная функция была объявлена.
Пример:
// global scope (глобальная область видимости) var num = 15; function outputNum() { console.log(num); } outputNum(); // 15

В приведённом примере во время вызова функции outputNum
будет создана локальная область, а также установлено в качестве значения [[Scope]] область в которой данная функция была объявлена.
Т.к. переменной num
нет в текущей области, то интерпретатор посредством [[Scope]] перейдёт в область (в данном случае глобальную) и попытается найти её там. В этой области (глобальной) она есть. А это значит, что в качестве num
будет использоваться значение переменной num
, находящейся в глобальной области видимости.
JavaScript всегда начинает поиск переменной или функции с текущей области видимости. Если она в ней не будет найдена, то интерпретатор переместится к следующей области, указанной в [[Scope]], и попробует отыскать её там. После этого действия повторяются, т.е. при отсутствии искомой переменной или функции в просматриваемой области видимости, интерпретатор перемещается к следующей области посредством [[Scope]] и пытается обнаружить её там.
В результате поиск всегда заканчивается одним из двух нижеприведённых сценариев:
Первый сценарий: интерпретатор встретил искомое имя идентификатора в какой-нибудь области. В этом случае он берёт его значение. Поиск искомого имени идентификатора в других областях видимости, т.е. дальнейшие перемещения по [[Scope]], не выполняются.
Второй сценарий: интерпретатор не нашёл нужный идентификатор. В этом случае он бросает ошибку о том, что данный идентификатор не определён. Данный сценарий может возникнуть только тогда, когда интерпретатор в поиске переменной или функции дошёл до глобальной области видимости и не нашёл её в ней.
Глобальная область видимости - это последнее звено в цепочке областей видимости. Она не содержит ссылку на другую область, дальше неё ничего нет.

Последовательность областей видимости, которые интерпретатор использует при разрешении имени идентификатора, называется в JavaScript цепочкой областей видимости (scope chain).
В этом примере интерпретатор при разрешении переменной color
дойдёт от текущей до глобальной области видимости:
// global scope (глобальная область видимости) var color = 'green'; function outputColor() { function displayColor() { console.log(color); } displayColor(); // "green" } outputColor();

В этом примере значение переменной drink
будет взято из локальной области, созданной во время вызова внешней функции:
// global scope (глобальная область видимости) var drink = 'молоко'; function outputDrink() { var drink = 'яблочный сок'; function displayDrink { console.log(drink); } displayDrink(); // "яблочный сок" } outputDrink();
В этом примере показано то, что в глобальной области видимости нельзя обратиться к переменным и функциям, находящимся в локальной области видимости.
var v1 = 15; function f1() { var v2 = 10; function f2() { console.log('f2'); } } console.log(v1); // 15 f1(); // вызов функции f1 f2(); // нельзя вызвать функцию f2, так как она не видна в глобальной области видимости console.log(v2); // нельзя обратиться к v2, так как она не видна в глобальной области видимости
При разрешении имени, интерпретатор начинает поиск, начиная с текущей области видимости. Поэтому этот пример выведет в консоль текст "Дима".
// global scope var name = 'Петя'; function getName() { var name = 'Иван'; function displayName() { var name = 'Дима'; console.log(name); // "Дима" } displayName(); // вызов функции displayName } getName(); // вызов функции getName
Если в функции displayName не была бы объявлена переменная name, то интерпретатор не обнаружил бы её в области, созданной во время вызова этой функции. В этом случае он взял бы значение из области видимости, созданной во время вызова функции getName. В результате в консоль был бы выведен текст "Иван".
В этом примере функция f1 объявлена в глобальной области видимости. Поэтому свойство [[Scope]] функции f1, во время её вызова, будет содержать ссылку на глобальную область видимости даже несмотря на то, что данная функция вызвана в локальной области видимости, созданной во время вызова функции f2.
// global scope var num = 10; function f1() { console.log(num); } function f2() { num = 20; f1(); // 10, т.к. [[Scope]] = global scope } f2(); // [[Scope]] = global scope
Поднятие (hoisting) функций и переменных
Функции вида function declaration можно использовать в JavaScript до их объявления. Это происходит из-за того что они поднимаются (hoisting) или другими словами «перемещаются» в начало текущего контекста.
// вызвать функцию greet greet(); // объявление функции greet function greet() { console.log('Привет!'); }
Но поднятие в JavaScript выполняется не только функций вида function declaration, но и переменных, объявленных с помощью ключевого слова var. При этом поднятие осуществляется только самого объявления переменной.
console.log(width); // undefined var width = 500;
Вышеприведённый код после поднятия будет аналогичен следующему:
var width; console.log(width); // undefined width = 500;
Локальные и глобальные переменные
В JavaScript переменные, созданные в глобальной области видимости называются глобальными, а переменные созданные в локальной области видимости соответственно локальными.
Иными словами, глобальные переменные — это переменные, объявленные вне тела какой-либо функции, а локальные — это переменные, объявленные внутри тела какой-либо функции.
// глобальная область видимости var age = 32; // глобальная переменная function sayHello() { // объявление функции sayHello var myName = "Вася"; // локальная переменная (не будет доступна вне этой функции) console.log("Привет, " + myName); } function sayAge() { // обявление функции sayAge console.log("Мне, " + age + " года"); // будет использоваться значение глобальной переменной, т.к. локальной переменной с именем age нет } // вызов функции sayHello sayHello(); // Привет, Вася // вызов функции sayAge sayAge(); // Мне 32 года console.log("Возраст: " + age); //Возраст: 32 console.log("Моё имя: " + myName); // Ошибка, переменная myName не определена

В JavaScript до ES6 блочных областей видимости не было. Т.е. любая переменная созданная с помощью ключевого слова var внутри блока будет видима и за его пределами.
Пример:
if (true) { var subject = "Математика"; // переменная будет видна за пределами данного блока } console.log(subject); // "Математика"
В ES6 были введены ключевые слова let и const. Они предназначены для создания переменных и констант, видимость которых будет ограничено блоком в котором они объявлены. Блочная область видимости в JavaScript определяется посредством фигурных скобок.
Например, переменная subject
не будет видна за пределами блока:
{ let subject = "Математика"; // переменная не будет видна за пределами данного блока } console.log(subject); // Uncaught ReferenceError: subject is not defined
Кроме этого переменные, объявленные с помощью let и константы, созданные посредством const в JavaScript не поднимаются (hoisting). Т.е. к ним нельзя обратиться до их непосредственного объявления.
{ console.log(osName); // Uncaught ReferenceError: subject is not defined let osName = "Linux"; } console.log(subject);
Контекст функции. Ключевое слово this
Под контекстом функции понимается объект, в рамках которого выполняется функция. Обратиться к этому объекту внутри функции можно с помощью ключевого слова this
.
Если функция не является методом объекта, то this
будет указывать на window
(глобальный объект).
function myFunc() { return this; } console.log(this); // window
В строгом режиме this
в вышеприведённом примере будет равно undefined
.
'use strict'; function myFunc() { return this; } console.log(this); // undefined
Если функция – это метод объекта, то this
внутри тела функции будет указывать на него.
Пример 1:
var myObj = { propA: 27, methA: function() { return this.propA; // this = myObj } } myObj.methA(); // 27
Пример 2:
// объект myFish var myFish = { fish : 'Треска', getFish: function() { // функция (метод объекта myFish) return this.fish; }, getOwnObject: function() { // функция (метод объекта myFish) return this; } } // вывести в консоль результат выполнения метода getFish console.log(myFish.getFish()); // "Треска" // вывести в консоль результат выполнения метода getOwnObject console.log(myFish.getOwnObject()); // объект myFish
Указание контекста функции. Методы call и apply
В JavaScript можно явно указать контекст, в котором необходимо вызвать функцию.
Первый способ - это использовать метод call
.
Синтаксис:
// func - функция // context - контекст, в котором нужно вызвать функцию // arg1, arg2, ... - список аргументов, которые нужно передать функции funс.call(context[, arg1[, arg2[, ...]]]);
Пример:
// объект user var user = { name: 'Василий', age: 27 } // объявление функции getUserAge function getUserAge() { return this.age; } // вызов функции в контексте объекта user console.log(getUserAge.call(user)); // 27
Второй способ - это использовать метод apply
. Данный метод аналогичен call
. Единственное отличие apply
от call
заключается в том, что аргументы в нём указываются посредством массива. Этот вариант передачи аргументов является очень удобным, особенно тогда когда их количество заранее неизвестно.
Синтаксис:
// func - функция // context - контекст, в котором нужно вызвать функцию // argArray - массив аргументов, которые нужно передать функции funс.apply(context[, argArray]);
В качестве контекста методам call
и apply
, кроме объекта, можно ещё установить специальные значения null
и undefined
.
Не в строгом режиме в этом случае this будет указывать на объект window.
В строгом режиме this будет равно null.
Привязка функции к контексту (метод bind)
Функция (метод) в JavaScript не сохраняет контекст (объект) к которому она относится.
В этом очень просто убедиться, если, например, сначала сохранить ссылку на функцию (метод) в некоторую переменную. А после этого вызвать эту функцию, используя данную переменную.
Например:
'use strict'; var mouse = { model: 'M100', getModel: function() { return this.model; } } // сохраним в переменную ссылку на метод var getModelMouse = mouse.getModel; // вызовем метод getModelMouse и выведем его результат в консоль console.log(getModelMouse()); // Uncaught TypeError: Cannot read property 'model' of undefined
В результате вызова getModelMouse
будет возвращена ошибка, т.к. контекст, к которому относится данный метод, будет потерян.
Метод bind
предназначен для явной привязки контекста (this
) к функции. Он в отличие от методов call
и apply
не вызывает функцию. Механизм этого метода заключается в создании функции-обёртки, которая будет устанавливать необходимый контекст целевой функции.
Кроме этого, эта функция-обёртка будет результатом, который метод bind будет возвращать в результате своего выполнения.
Метод bind
также позволяет задать аргументы, которые необходимо передать функции в момент её вызова. Данные аргументы будут переданы целевой функции до аргументов, которые ей будут установлены явно при её вызове.
Синтаксис метода bind
:
// func - функция // context - контекст, с которым нужно связать функцию // arg1, arg2, ... - список аргументов, которые нужно передать функции func.bind(context[, arg1[, arg2[, ...]]])
Например, изменим вышеприведённый пример. А именно, не просто сохраним ссылку на метод, а выполним это с привязкой его к объекту mouse:
// сохраним в переменную ссылку на метод с привязкой его к контексту mouse var getModelMouse = mouse.getModel.bind(mouse); // вызовем метод getModelMouse и выведем его результат в консоль console.log(getModelMouse()); // "M100"
В этом примере привяжем функцию changeScore к разным объектам. Функция changeScore, привязанная к первому объекту будет доступна по идентификатору changeScoreUser0001, а ко второму - по changeScoreUser0002.
'use strict'; function changeScore(amount) { this.score = this.score + amount; console.log(this.score); } var user0001 = { name = 'Афанасий', score = 1300 } var user0001 = { name = 'Анастасия', score = 2500 } var changeScoreUser0001 = changeScore.bind(user0001); var changeScoreUser0002 = changeScore.bind(user0002); changeScoreUser0001(400); // 1700 changeScoreUser0001(-800); // 900 changeScoreUser0002(-700); // 1800 changeScoreUser0001(300); // 2100
В этом примере, рассмотрим как осуществляется передача аргументов функции, привязанной к некоторому объекту с помощью bind.
'use strict'; function checkInRange(min, max, value) { if (typeof value !== 'number') { return false; } return value >= min && value <= max; } var checkInRange10_20 = checkInRange.bind(null, 10, 20); console.log(checkInRange10_20(15)); // true console.log(checkInRange10_20(25)); // false
var myName = "Женя"; // глобальная переменная, т.к. объявлена вне тела какой-либо функции function sayHello() { // объявление функции var myName1 = "Вася"; // локальная переменная console.log("Привет, " + myName); console.log("Привет, " + myName1); function sayHi() { // функции, объявленная внутри тела функции sayHello var myName2 = "Дима"; // локальная переменная console.log("Привет, " + myName); console.log("Привет, " + myName1); console.log("Привет, " + myName2); } sayHi(); } sayHello();

В локальной области видимости, созданной во время выполнения функции sayHi
можно получить значение переменной myName, myName1 и myName2. Переменная myName находится в текущей области выполнения,
myName2
- расположена в текущем контексте;myName1
- расположена в родительской функцииsayHello
;myName
- расположена в глобальном контексте.
А вот в функции sayHello()
доступны только 2 переменные:
myName1
- расположена в текущем контексте;myName
- расположена в глобальном контекст.
Рассмотрим ещё один пример:
// глобальный контекст var myName = "Женя"; //глобальная переменная function sayHello() { // контекст функции console.log("Привет, " + myName); // переменная name берётся из глобального контекста } function sayHi() { // контекст функции var myName = "Дима"; // локальная переменная функции sayHi console.log("Привет, " + myName); // переменная name берётся из текущего контекста } // вызов функции sayHi sayHi(); // вызов функции sayHello sayHello();

Функция sayHi()
выведет в консоль "Привет, Дима". Это обусловлено тем, что переменная name
существует в текущем контексте.
Функция sayHello()
отобразит в консоли запись "Привет, Женя". Переменная name
найдена в родительском (глобальном) контексте.
В языке JavaScript есть небольшая хитрость, которая заключается в том, что в функции можно всегда получить доступ к глобальной переменной, даже если имя этой переменной совпадает с локальной. Осуществляется это с помощью указания перед именем глобального контекста, т.е. window
.
myColor = "Зелёный"; //глобальная переменная // объявление функции getColor function getColor() { var myColor = "Синий"; //локальная переменная console.log("Цвет: " + myColor); //вывод значения локальной переменной console.log("Цвет: " + window.myColor); //вывод глобальной переменной } // вызов функции getColor getColor();

Ключевое слово var
При создании переменной почти всегда используется ключевое слово var
. Использование этого слова означает то, что переменная всегда добавляется в текущий контекст (область видимости). В функции - в контекст этой функции, вне какой-либо функции - в глобальный контекст.
Если Вы не используете строгий режим, то JavaScript позволяет создавать переменные без ключевого слова var
. С такими переменными необходимо быть очень осторожными. Это связано с тем, что такая переменная вне зависимости от контекста, в котором она создана, автоматически становится глобальной. А это может привести к непредсказуемым результатам.
Рассмотрим следующий пример:
function sayHello() { var myName = "Вася"; // переменная не существует вне этой функции } sayHello(); console.log("Привет, " + myName); //строчка выдаст ошибку: myName - не определена.
Если избавиться от ключевого слова var
, то тогда переменная будет объявлена как глобальная переменная:
function sayHello() { myName = "Вася"; // переменная будет глобальной, несмотря на то, что она объявлена в функции } sayHello(); console.log("Привет, " + myName); //строчка вернёт: Привет, Вася.
Некоторые особенности языка JavaScript при работе с переменными и функциями
У языка JavaScript есть одна интересная особенность, которая связана с тем как браузер понимает код, в котором использование переменной идёт до её объявления.
Рассмотрим эту ситуацию на следующем примере:
function sayHello() { console.log("Привет, " + myName); var myName = "Вася"; // переменная не существует вне этой функции } sayHello();
В этом примере вывод значения переменной myName
в консоль идёт до её объявления и инициализации.
Как браузер понимает этот код? Браузер объявления переменных всегда поднимает вверх их функциональной области видимости.
Т.е. браузер будет видеть вышепредставленный код следующим образом:
function sayHello() { var name; console.log("Привет, " + name); name = "Вася"; } sayHello();
В результате в консоли отобразится сообщение "Привет, undefined".
Кроме переменных, вверх их функциональной области видимости браузер поднимает ещё функции. Т.е. код JavaScript позволяет вызвать функцию до её объявления.
var name = "Вася"; sayHello(); // вызов функции sayHello // объявление функции sayHello function sayHello() { console.log(name); }
Вышеперечисленные особенности языка необходимо знать и по возможности их не использовать в коде. Т.к. это может привести к тому, что код станет более трудночитаемым. В результате это может привести к ошибках.
Поэтому следует придерживаться следующих правил:
- Переменные и функции объявлять до их использования.
- По возможности не использовать глобальные переменные или свести их количество к минимуму.
Комментарии ()