Область видимости

1. Область видимости

Область видимости (scope) — это независимая от языка концепция, которая описывает доступность переменных в исполняемом коде.

Scope chain (цепочка областей видимости) - области видимости образуют иерархию, так что дочерние области имеют доступ к переменным из родительских областей, но не наоборот.

Есть три типа областей видимости:

  • Переменные, объявленные на самом верхнем уровне, то есть вне любых конструкций вроде if, while, for и функций, находятся в глобальной области видимости.

  • Переменные, объявленные внутри инструкций if, циклов и других блоков кода находятся в блочной области видимости.

  • Переменные, объявленные внутри функций, находятся в области видимости функции.

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

// Global scope

function foo() {
  // Local function scope of foo

  if (true) {
    // Local block scope
  }

  // Local function scope of foo

  function bar() {
    // Local function scope of bar
  }

  // Local function scope of foo
}

// Global scope
Copy

Интерпретатор пытается сначала найти переменную в той области видимости, в которой к ней обратились. Если такой переменной в локальной области видимости нет, то он выходит наружу, на один уровень за попытку, пока не найдёт значение или не дойдет до самой верхней области видимости (глобальной) и поймет, что переменную с таким идентификатором невозможно найти, так как ее просто нет, тогда будет ошибка о том, что переменная не объявлена.

Функция add возвращает сумму a и b. Переменная a объявлена внутри функции, b нет.

const b = 10;

const add = function () {
  const a = 5;

  return a + b;
};

add(); // 15
Copy

Пытаясь решить выражение a + b, интерпретатор ищет значения a и b. Поиск начинается внутри локальной области видимости — внутри функции add. Он находит значение a и переходит к b. Невозможно найти значение b в локальной области видимости, поэтому поиск расширяется до наружной области. Тут он находит b — это 10. Выражение a + b превращается в 5 + 10, в результате получаем 15.

2. Область видимости функции

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

Глобальный value объявлен вне тела функции и его значение будет выведено в консоль. Локальный value внутри функции add по прежнему виден только внутри этой функции. Эти два value не имеют ничего общего друг с другом, они находятся в разных областях видимости. Они не схлопываются в одно целое, не смотря на то, что у них одно и то же имя. В свою очередь innerValue не доступна вне тела функции.

const value = 50;

const add = function (num) {
  const value = 10;
  const innerValue = 5;

  return num + value + innerValue;
};

// value объявлен глобально и поэтому доступен
console.log(value); // 50

console.log(add(20)); // 35

// Ошибка, переменная innerValue не объявлена в этой области
// видимости, она доступна только внутри функции add
console.log(innerValue);
Copy

Такое поведение помогает предотвратить случайное взаимодействие между функциями. Если бы все переменные использовались в любом месте программы, было бы очень трудно убедиться, что одна переменная не используется для разных операций.

Рассматривая локальные для функции переменные как существующие только внутри функции, язык делает возможным работу с функциями как с изолированными контейнерами, что позволяет не волноваться про весь код целиком.

3. Hoisting

В языках программирования, в том числе в JavaScript, код исполняется в две фазы.

Фаза компиляции, интерпретации или оценки (compile time, evaluation time) - подготовка перед исполнением кода, проверка валидности синтаксиса исходного кода.

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

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

Если эта фаза завершилась успешно, значит скрипт написан без явных ошибок и закончил свою работу. На этой фазе могут быть ошибки, связанные с отсутствующими свойствами и переменными, преобразованием типов и т. д., то есть что-то, что происходит только во время выполнения кода.

Попробуйте выполнить следующий код. Так как мы сделали опечатку и вместо const пытаемся объявить переменную value ключевым словом cos, на фазе компиляции будет выявлена синтаксическая ошибка и фаза исполнения даже не запустится. В консоли мы сразу увидим сообщение об ошибке.

console.log('Этого сообщения не будет в консоли.');

cos value = 5;
Copy

Поднятие переменных (hoisting) - это механизм интерпретатора, который, до фазы исполнения кода, поднимает объявления переменных в начало области видимости (блочной или функции) в которой они были объявлены.

Именно поэтому работает function declaration и так странно ведут себя переменные, объявленные используя var - их объявления поднимаются в начало области видимости функции в которой они были объявлены.

// Вот поэтому мы используем let или const

console.log(value); // undefined
value = 5;

if (true) {
  console.log(value); // 5
  var value = 10;
  console.log(value); // 10
}

value = 15;
console.log(value); // 15
Copy

Переменные, объявленные используя let или const так же поднимаются, но при этом подчиняются блочной области видимости, ничем не инициализируются по умолчанию и не доступны для обращения до того места в коде, где были объявлены в коде.

// В каждой области видимости будет создана своя, независимая переменная value

console.log(value); // ReferenceError: value is not defined
const value = 5;
console.log(value); // 5

if (true) {
  console.log(value); // ReferenceError: value is not defined
  const value = 10;
  console.log(value); // 10
}

console.log(value); // 5
Copy

Более детально про поднятие идентификаторов и ключевые словах var, let и const читайте в этой статье.

Last updated