Область видимости и контекст в JavaScript

В этой статье мы изучим, что такое области видимости, глобальные и локальные переменные, контекст и ключевое слово this.
Области видимости
Область видимости – это некоторая сущность JavaScript, которая определяет границы действия переменных.
Создаются области видимости во время выполнения программы. Самая первая область, которая создаётся и которая включает в себя все остальные называется глобальной.
Именно в этой области определены такие переменные как window
в веб-браузере и global
в Node.js.
Вы также можете определять переменные в этой области. Для этого достаточно просто объявить переменные вне блока, функции и модуля. В этом случае они будут находиться в глобальной области видимости:
let num = 5;
const fruits = ['apple', 'pears', 'banana'];
Переменные объявленные в глобальной области видимости называются глобальными переменными. Такие переменные могут быть доступны в любой точке программы.
Кроме глобальной области видимости в JavaScript имеются ещё локальные. Они, создаются, когда интерпретатор, например, выполняет код блочной конструкции:
// глобальная переменная
let a = 5;
{
// локальная переменная
let b = 17;
}

Причем такая локальная область видимости называется областью видимости блока.
Переменные, объявленные внутри блока с помощью let
и const
имеют область видимости ограниченную этим блоком. Т.е. они привязаны к нему и будет действовать только в его рамках. Переменные, объявленные в локальной области видимости называются локальными.
if (true) {
// локальная переменная
let b = 17;
// выведем значение переменной b в консоль
console.log(b); // 17
}
console.log(b); // Uncaught ReferenceError: b is not defined
Под блоком в JavaScript понимается любой код, который расположен в фигурных скобках { ... }
. Блоки используются в конструкциях if
, for
, while
и т.д. Даже тело функции является блоком, т.к. находится между фигурными скобками.
Кроме этого локальные области видимости также создаются вызовами функций и модулями. Они соответственно называются областью видимости функции и областью видимости модуля.
Пример, в котором создаётся две функциональные области видимости:
// глобальная область видимости
function salute(welcomeText) {
console.log(welcomeText);
}
salute('Привет'); // вызов функции salute
salute('Здравствуйте'); // вызов функции salute
В этом примере в глобальной области видимости объявляется функция salute
с помощью ключевого слова function
. Затем эта функция вызывается два раза.
Область видимости функции создаётся для каждого вызова функции. Даже, когда мы вызываем одну и ту же функцию. При этом для каждого вызова создаётся своя отдельная область видимости.
В этом примере будут созданы две локальные области видимости уровня функции.

Цепочка областей видимости
При создании локальной области видимости она всегда сохраняет ссылку на внешнюю область видимости. Эта ссылка используется для поиска переменных.
// глобальная область видимости
let a = 5;
let b = 8;
let c = 20;
function fnA() {
a = 7;
b = 10;
let b = 11;
b = 13;
function fnB() {
let c = 25;
console.log(a); // 7
console.log(b); // 13
console.log(c); // 25
}
fnB();
}
fnA();
В момент выполнения console.log(a)
мы имеем следующую картину:

Начинается этот код с создания переменных a
, b
, c
и fnA
с соответствующими значениями в глобальной области видимости.
После этого вызывается функция fnA()
. При её вызове создаётся область видимости функции, которая имеет ссылку на внешнюю область. В данном случае ей является глобальная область видимости.
Далее переменной a
присваивается значение 7
.
a = 7;
Но перед тем, как присвоить ей значение, нам необходимо сначала её найти. Поиск переменной всегда начинается с текущей области видимости. Но переменной и параметра a
в области видимости вызова функции fnA()
нет. Поэтому мы переходим по ссылке, в данном случае ведущую в глобальную область видимости и ищем переменную там. В данном примере такая переменная здесь имеется, и мы присваиваем ей значение 7
. Таким образом, на этом шаге мы внутри функции fnA
присвоили новое значение глобальной переменной a
.
На следующей строчке мы присваиваем переменной b
значение 10
:
b = 10;
На текущий момент у нас ещё нет объявленной переменной b
b в текущей области видимости. Поэтому мы также переходим по ссылке в глобальную область видимости и находим эту переменную там. После этого задаём ей новое значение. На этом этапе выполнения кода у нас в глобальной области видимости переменные a
и b
имеют соответственно значения 7 и 10.
Затем мы объявляем переменную b
в локальной области функции, созданной вызовом fnA()
и в этом же выражении сразу же ей присваиваем число 11
:
let b = 11;
Несмотря на то, что переменная b
есть в глобальной области видимости, мы можем создавать переменные с таким же именем в локальных областях видимости. После этого действия переменная b
, созданная в области видимости функции будет пересекаться с переменной b
, объявленной в глобальной области видимости, т.к. они имеют одинаковые имена.
Теперь, если мы попытаемся в этой области видимости получить доступ к переменной b
, то получим переменную, объявленную в этой локальной области видимости, но никак не переменную b
из глобальной области видимости. Т.е. после объявления b
в этой области видимости нам уже будет не доступна переменная b
, объявленная в глобальной области видимости.
Таким образом, на следующей строчке будет использоваться переменная b
, объявленная в текущей области видимости:
b = 13;
После этого объявляется функция fnB
. Затем она вызывается fnB()
и интерпретатор создаёт новую область видимости внутри fnA
. Эта область видимости в свою очередь тоже содержит ссылку на внешнюю по отношению к ней область видимости. В данном случае, на ту, которая была создана ранее при вызове fnA()
. В итоге у нас получается цепочка областей видимости.
Таким образом, цепочкой областей видимости (scope chain) можно назвать последовательность областей видимости, которые интерпретатор JavaScript использует для поиска переменных. При этом поиск всегда начинается с текущей области видимости и если только она не найдена в текущей, то происходит переход к следующей по цепочке и поиск переменной там и т.д.
На строчке console.log(a)
для вывода значения указанной переменной в консоль, её сначала нужно получить. Поиск переменной, как мы уже отмечали выше, всегда начинается с текущей области видимости. Но так как этой переменной здесь нет, то выполняется переход по ссылке к следующей области, которая является по отношению к текущей внешней.
В этой области (в данном случае созданной в результате вызова fnA()
) переменной a тоже нет. Но есть ссылка на следующую область, которая в данном случае является глобальной. Переходим по ней и пытаемся найти переменную там. В ней эта переменная есть. А, следовательно, берём эту переменную и выводим её значение в консоль. В данном случае, число 7
.
Итак, поиск переменной интерпретатор JavaScript всегда начинает с текущей области видимости. Если она в ней имеется, то поиск прекращается и берётся эта переменная. В противном случае интерпретатор в поиске переменной переместится к следующей области, содержащейся в ссылке, и попробует отыскать её там. После этого действия повторяются, т.е. при отсутствии искомой переменной в просматриваемой области видимости, интерпретатор перемещается к следующей области посредством ссылки и пытается обнаружить её там.
В результате поиск всегда заканчивается одним из двух нижеприведённых сценариев:
- интерпретатор нашёл искомую переменную в какой-нибудь области; в этом случае он берёт эту переменную и останавливает её дальнейший поиск по цепочке в других областях видимости;
- интерпретатор в поиске переменной дошёл до глобальной области и не нашёл её там; в этом случае возникает ошибка, что переменной с указанным именем не существует.
Глобальная область видимости - это последнее звено в цепочке областей видимости Она не содержит ссылку на другую область, на ней всё заканчивается.
Рассмотрим ещё один очень интересный пример:
let num = 10;
function fnA() {
console.log(num);
}
function fnB() {
let num = 20;
fnA();
}
fnB();
В этом примере в глобальной области видимости объявлены переменные num
, fnA
и fnB
. При вызове функции fnB()
у нас создаётся локальная область видимости, которая будет содержать ссылку на внешнюю, в данном случае на глобальную область видимости. В этой области у нас создаётся переменная с таким же именем num
, а затем вызывается функция fnA()
. При вызове функции fnA()
у нас создаётся локальная область видимости, которая будет иметь в качестве ссылки глобальную область. Почему так? Потому что внешняя область определяется в зависимости от того, где объявлена функция, а не вызвана. А так как функция объявлена в глобальной области видимости, то не зависимого того где она вызвана, она будет содержать в качестве ссылки - ссылку на внешнюю область видимости, в зависимости от того где она объявлена.
Контекст и ключевое слово this
Кроме области видимости в JavaScript имеется ещё контекст (context). Контекст – это то, на что указывает this
.
По сути this
– это объект, которому «принадлежит» выполняемый в данный момент код.
1. В контексте глобального объекта (вне модулей и функций) this
– это глобальный объект.
let num = 10;
console.log(this); // window
2. Внутри функции this
зависит от того, как вызывается функция.
2.1. Если функция вызывается не как метод объекта, то this
в не строгом режиме указывает на глобальный объект, а в строгом – undefined
.
function fnA() {
console.log(this); // window
}
function fnB() {
'use strict';
console.log(this); // undefined
}
fnA();
fnB();
2.2. Когда функция вызывается как метод, this
– это объект, который использовался для его вызова:
Object.prototype.getThis = function () {
return this;
}
const objectA = {}
const objectB = {}
console.log(objectA.getThis() === objectA); // true
console.log(objectB.getThis() === objectB); // true
3. Внутри класса this
указывает на новый объект, который будет создан с помощью new
:
class Person {
getThis() {
return this;
}
}
const personA = new Person();
const personB = new Person();
console.log(personA.getThis() === personA); // true
console.log(personB.getThis() === personB); // true
4. В модуле на верхнем уровне this
– это undefined
.
<script type="module">
console.log(this);
</script>
Стрелочные функции нет имеют собственного this
. Если внутри стрелочной функции происходит обращение к this
, она берёт его снаружи.
Указание контекста с помощью call или apply
В JavaScript при вызове функции можно установить нужный this
, т.е. контекст, в котором она должна выполняться. Осуществляется это с помощью метода call
или apply
.
Синтаксис использования метода call
:
// myFunc – некоторая функция
// thisArg – значение this
// arg1, arg2, ..., argN – аргументы для функции
myFunc.call(thisArg, arg1, arg2, ..., argN);
Например:
// объект user
const user = {
name: 'Василий',
age: 27
}
// объявление функции getAge
const getAge = function() {
return this.age;
}
// вызов функции в контексте объекта user
console.log(getAge.call(user)); // 27
Метод apply
аналогичен call
. Единственное отличие в том, что аргументы в apply
передаются в виде массива.
Синтаксис метода apply
:
// myFunc – некоторая функция
// thisArg – значение this
// argsArray – аргументы в виде массива
myFunc.call(thisArg, argsArray);
В качестве thisArg
методам call
и apply
, кроме объекта, можно также установить значение null
или undefined
. В не строгом режиме эти значения будут заменены ссылкой на глобальный объект.
Привязка контекста к функции
У функций имеется метод bind
. С помощью него можно установить определённый this
, в рамках которого она должна выполняться. В качестве результата bind()
возвращает новую связанную с этим this
функцию. Выполнение связанной функции приводит к вызову исходной функции, но в указанном this
.
Синтаксис метода bind
:
// newFn – новая функция, которая будет выполнять fn в указанном this
// fn – исходная функция
// thisArg – значение this
// arg1, ... , argN – аргументы для функции
const newFn = fn.bind(thisArg, arg1, ... , argN)
1. Использование bind
для привязки функции:
let person = {
fullName: 'Alexander Maltsev',
getFullName() {
console.log(this.fullName);
}
};
setTimeout(person.getFullName.bind(person), 1000); // Alexander Maltsev
Так как функция setTimeout
является методом объекта window
, то внутри неё this
указывает на глобальный объект. Чтобы this
внутри функции был person
, его нужно привязать с помощью метода bind()
. То есть, как это и сделано в примере.
2. Применение bind
для использования методов другого объекта:
// объект runner имеет метод run
const runner = {
name: 'Бегун',
run(speed) {
console.log(this.name + ' бежит со скоростью ' + speed + ' километров в час.');
}
};
// объект flyer имеет метод fly
const flyer = {
name: 'Флайер',
fly(speed) {
console.log(this.name + ' летит со скоростью ' + speed + ' километров в час.');
}
};
const fly = flyer.fly.bind(runner, 50);
fly(); // Бегун летит со скоростью 50 километров в час.
В этом примере показано как можно использовать метод одного объекта для другого без создания в нём его копии.
3. Создание функции с заранее заданными начальными аргументами:
const sum = function (a, b) {
console.log(a + b);
}
const add7 = sum.bind(null, 7);
add7(5); // 12
add7(9); // 16
4. Привязывания одной функции к разным объектам:
const users = {
'm-1': { total: 1000 },
'm-2': { total: 700 }
}
const getFee = function (fee) {
this.total -= fee;
console.log(this.total);
}
const getFeeM1 = getFee.bind(users['m-1']);
const getFeeM2 = getFee.bind(users['m-2']);
getFeeM2(100); // 600
getFeeM2(100); // 500
В этом примере мы с помощью bind
привязали одну функцию getFee
к разным объектам. Для объекта users['m-1']
её следует использовать как getFeeM1
, а для users['m-2']
– как getFeeM2
.
Ключевое слово var
Создавать переменные посредством var
и функций посредством Function Declaration не рекомендуется. Но понимать, как работает код, в котором эти вещи используются необходимо.
1. Переменные, объявление с помощью ключевого слова var
, имеют область видимости функции:
{
var a = 5;
let b = 7;
function myFunc() {
var c = 5;
}
}
myFunc();
console.log(a); // 5
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // Uncaught ReferenceError: c is not defined
Т.е. var
в отличие от let
и const
создаёт переменные, которые ограничены областью видимости функции, а не блоком.
2. Ключевое слово var
в отличие от let
и const
, создаёт глобальные переменные, которые будут являться свойствами глобального объекта window
в веб-браузере и global
в Node.js:
var a = 5;
let b = 7;
console.log(window.a); // 5
console.log(window.b); // undefined
Переменные, созданные в глобальной области видимости с помощью ключевых слов let
, const
и class
, не являются свойствами глобального объекта.
3. Переменные, объявленные с использованием var
поднимаются (hoisting) в начало текущего контекста. При этом поднимается только само объявление переменной:
console.log(`Ширина: ${width}`); // Ширина: undefined
var width = 500;
Этот код интерпретатор воспринимает так:
var width;
console.log(width); // Ширина: undefined
width = 500;
Но в JavaScript поднимаются не только переменные, созданные с помощью var
, но и функции, объявленные как function declaration:
// вызываем функцию greet
greet();
// объявляем функцию greet
function greet() {
console.log('Привет!');
}
В этом примере мы вызываем функцию greet()
до её объявления.
Необъявленные переменные и строгий режим
При попытке присвоить значение переменной, которая раньше нигде не была объявлена, ошибки не будет:
function sayHello() {
myName = 'Вася';
console.log(`Привет, ${myName}!`);
}
sayHello(); // Привет, Вася!
console.log(myName); // Вася
Переменная myName
в этом примере будет объявлена автоматически, причём это будет сделано в глобальной области видимости. Тем самым после завершения выполнения функции sayHello()
, эта переменная будет нам доступна и мы можем вывести её значение в консоль.
Таким образом, когда мы пытаемся присвоить значение переменной, которая нигде не была найдена, такая переменная будет создана автоматически при этом в глобальной области видимости.
Но писать код без объявления переменных не рекомендуется, и чтобы избежать их автоматического создания можно использовать строгий режим (на английском strict mode).
Строгий режим – это просто инструкция интерпретатору JavaScript, которая предотвращает выполнение определенных действий и создает больше исключений.
Для того чтобы включить строгий режим достаточно просто добавить следующую строку:
'use strict';
Строгий режим можно включить ко всему сценарию или к отдельным функциям и модулям.
Чтобы применить строгий режим ко всему сценарию, просто добавьте в самом вверху кода перед первой строчкой 'use strict'
.
После включения строго режима при выполнении примера, приведённого выше, вы получите ошибку:
function sayHello() {
myName = 'Вася'; // Переменная не
console.log(`Привет, ${myName}!`);
}
sayHello();
console.log(myName);
Таким образом, строгий режим запрещает использование не объявленных переменных.
var drink = 'молоко';
function outputDrink() {
var drink = 'яблочный сок';
function displayDrink {
console.log(drink);
}
displayDrink();
}
outputDrink();
itchief.ru/assets/uploadify/3/e/b/3ebb568b32113bd8bbd8c04d2ea4697f.png
name = 'Афанасий',
score = 1300
}
var user0001 = {
name = 'Анастасия',
score = 2500
}
Брат, привет, в разделе про bind во втором примере ключи объектов записаны через = а не через:
и я правильно понимаю что второй объект должен называться user0002?
я просто чтобы понять хорошо твою статья переписываю слово в слово) и код весь проверяю)
10 будет, если, например, var воткнуть перед num = 20:
думаю, что-то такое и хотели написать