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

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

• Область видимости (scope) — это широкое понятие, означающее, грубо говоря, «интерпретатор в разных местах кода видит разные штуки».

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

• Окружение (environment) — это область памяти, где записываются идентификаторы и значения из областей видимости. Не путайте с окружением, как средой исполнения.
Путаница возникает ещё потому, что в разделе про Область видимости мы смотрим на примеры, которые работают по правилу «лексическая область видимости». От этого никуда не деться, так как язык 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, но после того как вызывается changera становится 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.

Это замыкание: сочетание функции и окружения, где она была заявлена.
Дополнительные материалы
Тесты
Пройти тест
Может ли программа содержать несколько функций? (доверьтесь своим инстинктам ;)
Верно!
Неверно
Дальше
Проверить
Завершить тест
Могут ли функции возвращать строки (тексты), или они могут возвращать исключительно числа?
Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const height = findHeight();

Какое значение будет "записано" в константу height после выполнения?

Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const someFunction = (x) => {
  return 10 * 42;
};


const y = someFunction(9281);

Что будет "сохранено" в константе y после выполнения?
Неверно
Верно!
Неверно
Дальше
Проверить
Завершить тест
Каким будет результат вызова функции?

 const firstNum = 10;
 const secondNum = 5;


 const sum = (z, g) => z + g;


 sum(firstNum, secondNum);
Верно!
Неверно! Попробуйте еще раз
Неверно! Попробуйте еще раз
Дальше
Проверить
Завершить тест
Дана такая функция:

const sum = (a, b, c) => {
  return a + b + c;
};

Можно ли в этой ситуации использовать сокращенный синтаксис записи функции?
Неверно
Верно!
Дальше
Проверить
Завершить тест
Пройти еще раз
Пройти еще раз
Пройти еще раз
Пройти еще раз
Упражнение
Функции lessThan, greaterThan и isEqual в модуле comparers сравнивают две строки и возвращают true/false.

Сравнение идет по количеству заглавных символов в строке (больше заглавных — больше строка).

Специальные символы (например, пробел) не имеют заглавных эквивалентов и в данном задании считаются заглавными.

Примеры:

greaterThan('AD', 'ad sd'); // true, сравнение на > (больше)
greaterThan('AD', '   Ad sd'); // false, сравнение на > (больше)
lessThan('ghe df', 'dfwe r D'); // true, сравнение на < (меньше)
isEqual('liSp', 'lisP'); // true
Допишите необходимые части функций bigLettersCount и compare для того, чтобы заработали функции lessThan, greaterThan и isEqual.

Функция compare, принимающая две строки first и second, работает по следующему алгоритму:

  • Если в первой строке больше заглавных символов, то возвращается 1.
  • Если во второй строке больше заглавных символов, то возвращается -1.
  • Иначе возвращается 0.
Подсказки
  • Функция bigLettersCount должна принимать на вход строку str и высчитывать количество заглавных символов в ней.
  • Вычисление длины строки: length(str).
  • Перевод строки в верхний регистр: toUpperCase(str).
  • Проверка на то, что символ в верхнем регистре: toUpperCase(char) === char;