Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Окружение

Видео может быть заблокировано из-за расширений браузера. В статье вы найдете решение этой проблемы.

Важное примечание

Изначально в начале конспекта и урока речь шла о глобальном и локальном окружении, но объяснялась фактически глобальная и локальная область видимости. В видео эта тема до сих пор называется «глобальное и локальное окружение», текстовая версия урока исправлена.

Прежде, чем приступить к уроку, разберёмся с терминологией:

  • Область видимости (scope) — это широкое понятие, означающее, грубо говоря, «интерпретатор в разных местах кода видит разные штуки».
  • Лексическая область видимости (Lexical scoping) — это конкретный механизм, одно из правил для области видимости. Этот механизм применяется в JavaScript и большинстве других языков. Под лексической областью видимости можно понимать просто механизм поиска значений: смотрим в текущей области, если нет — идём на уровень выше, и так далее. Слово «лексический» означает, что видимость задаётся исключительно текстом программы, исходным кодом. То есть можно смотреть на программу, не запуская её, и понять область видимости в любой точке. В других языках может быть не лексический механизм, а динамический (dynamic scope).
  • Окружение (environment) - это область памяти, где записываются идентификаторы и значения из областей видимости. Не путайте с окружением, как средой исполнения.

Упрощённая визуализация областей видимости и окружений в javascript

Путаница возникает ещё потому, что в разделе про Область видимости мы смотрим на примеры, которые работают по правилу «лексическая область видимости». От этого никуда не деться, так как язык JavaScript работает именно так.

Транскрипт урока

Часть I. Окружение

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

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

const age = 29;

const multiplier = (num) => {
  const x = 10;
  return num * x;
}

let result = true;

Константа age , функция multiplier и переменная result — имеют "глобальную область видимости". Область видимости означает "область, где компоненты доступны".

Внутри функции multiplier есть константа x. Поскольку она внутри блока кода, это локальная константа, а не глобальная. Она видна только внутри этой функции, но не снаружи. У неё локальная область видимости.

В функции multiplier есть ещё один компонент из локальной области видимости — аргумент num. Он не задан так же чётко, как константы или переменные, но ведёт себя почти как локальная переменная.

У нас нет доступа к x снаружи, как будто её там не существует:

const multiplier = (num) => {
  const x = 10;
  return num * x;
}

console.log(x);     // ReferenceError: x is not defined

console.log вызывается в глобальном окружении, а x не задан в этом глобальном окружении. Поэтому мы получаем Reference Error.

Мы можем задать x глобально:

const x = 55;

const multiplier = (num) => {
  const x = 10;
  return num * x;
}

console.log(x);     // 55

Теперь существует глобальный x и его значение было выведено на экран, но локальный x внутри функции multiplier по-прежнему виден только внутри этой функции. Эти два x не имеют ничего общего друг с другом, они находятся в разных областях видимости. Они не схлопываются в одно целое, несмотря на то, что у них одно и то же имя.

Любой блок кода между фигурными скобками имеет локальную область видимости. Вот пример с блоком if:

let a = 0;

if (a === 0) {
  const local = 10;
}

console.log(local); // => ReferenceError: local is not defined

То же работает для циклов while и for.

Ок, локальное не доступно снаружи. Но глобальное доступно везде. Даже внутри чего-то? Да!

let a = 0;

const changer = () => {
  a++;
}

console.log(a);   // 0
changer();
console.log(a);   // 1

Эта глобальная переменная a изменилась внутри функции changer. Функция вообще выполняет что-то только когда её вызывают, а не когда её определяют, так что вначале a это 0, но после того как вызывается changer, a становится 1.

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

Часть II. Лексическая область видимости

Взгляните на эту программу:

let a = 7;
let b = 10;

const multiplier = () => {
  let a = 5;
  return a * b;
}

multiplier(); // 50

Функция multiplier возвращает произведение a и b. a задано внутри, а b — нет.

Пытаясь решить умножение a * b, JavaScript ищет значения a и b. Он начинает искать локально и выходит наружу, по одной области видимости за шаг, пока он не найдёт то, что ему нужно или пока не поймёт, что это невозможно найти.

Поэтому в данном примере JavaScript начинает с поиска a внутри локальной области видимости — внутри функции multiplier. Он находит значение сразу и переходит к b. Невозможно найти значение b в локальной области видимости, поэтому он переходит к наружной области. Тут он находит b — это 10. a * b превращается в 5 * 10, а затем в 50.

Весь этот кусок кода мог бы быть внутри другой функции, и ещё внутри другой функции. И если бы b не нашлась здесь, JavaScript продолжил бы искать b за пределами функции, слой за слоем.

Заметьте, что a = 7 не затронула вычисления, a была найдена внутри, поэтому внешняя a не сыграла роли.

Этот механизм называется лексической областью видимости. Область видимости любого компонента определяется местом расположения этого компонента внутри кода. И вложенные блоки имеют доступ к их внешним областям видимости.

Часть III. Замыкания

Большинство языков программирования имеют что-то вроде области видимости или окружения, и этот механизм позволяет существовать замыканиям. Замыкание — это всего лишь модное название функции, которая запоминает внешние штуки, используемые внутри.

Перед тем, как мы продолжим, давайте вспомним как функции создаются и используются:

const f = () => {
  return 0;
}

f — довольно бесполезная функция, она всегда возвращает 0. Весь этот набор состоит из двух частей: константы и самой функции.

Важно помнить, что эти два компонента раздельны. Первый — константа с именем f. Её значение могло бы быть числом или строкой. Но в данном случае её значение — функция.

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

Когда вы вызываете эту функцию, вот так:

f();

… создаётся новый ящик, основываясь на описании на этом листе бумаги.

Ок, вернёмся к замыканиям. Рассмотрим следующий код:

const createPrinter = () => {
  const name = "King";

  const printName = () => {
    console.log(name);
  }

  return printName;
}

const myPrinter = createPrinter();
myPrinter();    // King

Функция createPrinter создаёт константу name и затем функцию с именем printName. Обе они локальные для функции createPrinter, и доступны только внутри createPrinter.

У самой printName нет локальных компонентов, но у неё есть доступ к области видимости, где она сама находится, внешней области, где задана константа name.

Затем функция createPrinter возвращает функцию printName. Помните, определения функций — это описания запущенных функций, просто фрагменты информации, как числа или строки. Поэтому мы можем вернуть определение функции, как мы возвращаем число.

Во внешней области видимости мы создаём константу myPrinter и задаём ей значение, которое возвращает вызов функции createPrinter(). Этот вызов возвращает функцию, так что теперь myPrinter — это функция. Вызовите её, и на экран выведется "King".

Тут есть странная штука: эта константа name была создана внутри функции createPrinter. Функция была вызвана и исполнена. Как мы знаем, когда функция заканчивает работу, она больше не существует. Этот магический ящик исчезает со всеми своими внутренностями.

НО он возвратил другую функцию, и уже она как-то запомнила константу name. Поэтому когда мы вызывали myPrinter(), она вывела "King" — запомненное значение, при том, что область видимости, где оно было задано больше не существует.

Функция, которая была возвращена из createPrinter, называется замыканием. Замыкание — это сочетание функции и окружения, где она была задана. Функция "замкнула" в себе некоторую информацию из области видимости.

Это может выглядеть как JavaScript-фокус, но замыкания, когда их используют разумно, могут сделать код приятней, чище и проще для чтения. И сама идея возврата функций тем же способом, которым можно возвращать числа и строки, даёт больше возможностей и гибкости.

Вы заметите, как часто эти идеи используются в программировании, и мы рассмотрим их мощь в следующих курсах.

Выводы

  • Область видимости (scope) компонентов — это местоположение, где эти компоненты доступны.
  • Компоненты, созданные снаружи функций, инструкций с if, циклов и так далее, находятся в глобальной области видимости
  • Фигурные скобки { } задают новую локальную область видимости

Глобальная против локальной

Локальные константы и переменные не видимы снаружи их области видимости:

const multiplier = (num) => {
  const x = 10;
  return num * x;
}

console.log(x);     // ReferenceError: x is not defined

Но если x представлен глобально, то он доступен:

const x = 55;

const multiplier = (num) => {
  const x = 10;
  return num * x;
}

console.log(x);     // 55

Возможен доступ к внешней области видимости:

let a = 0;

const changer = () => {
  a++;
}

console.log(a);   // 0
changer();
console.log(a);   // 1

Функция фактически производит что-то, только когда она вызывается, а не задаётся, поэтому изначально a это 0, но после того, как вызвана changer, a становится 1.

Лексическая область видимости

JavaScript пытается найти значение в текущем окружении. Но значение не находится и JavaScript выходит наружу, на один уровень за попытку, пока не найдёт значение или не поймет, что значение невозможно найти.

let a = 7;
let b = 10;

const multiplier = () => {
  let a = 5;
  return a * b;
}

multiplier(); // 50

Здесь, в выражении a * b , функция multiplier использует локальную a (потому что она обнаружена локально), и наружную b (потому что локально b найдена не была).

Замыкания

const createPrinter = () => {
  const name = "King";

  const printName = () => {
    console.log(name);
  }

  return printName;
}

const myPrinter = createPrinter();
myPrinter();    // King

myPrinter — это функция, которая была возвращена createPrinter. Несмотря на то, что вызов createPrinter окончен и константы name больше не существует, значение запомнено в myPrinter.

Это замыкание: сочетание функции и окружения, где она была заявлена.


Дополнительные материалы

  1. Variables and scoping / Exploring JS
  2. Scope / Wikipedia
  3. Closure / Wikipedia
  4. Closures in JS / Mozilla Developer Network

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг». Защита от спама reCAPTCHA «Конфиденциальность» и «Условия использования».