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

Скрытие переменных

Как разработчик, бывают случаи, когда вы хотите контролировать, как другие разработчики взаимодействуют с вашим кодом. Возможно, вы захотите убедиться, что определенные переменные в вашем коде изменяются только контролируемым образом. Взгляните на следующий пример:

const counter = {
 count: 0,
 increment(x = 1) {this.count = this.count + x},
 decrement(x = 1) {this.count = this.count — x},
 getCount() {console.log(this.count)}
}

У объекта счетчика есть свойство count и три метода. Два метода со свойством по умолчанию 1 для увеличения или уменьшения значения count и один метод для отображения значения count на консоли. Если мы выполним следующее:

counter.increment();
counter.increment(5);
counter.increment();
counter.increment(11);
counter.increment();
counter.decrement(14);
counter.getCount();

Значение 5 записывается в консоль. Однако, если мы выполним следующее:

counter.count++;
console.log(counter.count);

Значение переменной count увеличивается на единицу и регистрируется без использования каких-либо определенных методов.

В этом примере мы не контролируем, как другие разработчики взаимодействуют с нашим свойством count. Они могут использовать предоставленные нами методы, но они также могут напрямую обращаться к свойству count, вызывая counter.count.

Закрытие

Мы можем решить эту проблему с помощью закрытия. Замыкание создается, когда вы объявляете функцию в другой функции. Внутренняя функция имеет доступ к переменным и параметрам внешних функций. Если вы раскрываете внутреннюю функцию, вы можете получить доступ к переменной и параметрам внешних функций контролируемым образом через внутреннюю функцию. Один из способов раскрыть внутреннюю функцию - вернуть ее из внешней функции. Рассмотрим следующий код:

function counter() {
 let count = 0;
 const increment = (x = 1) => count = count + x;
 const decrement = (x = 1) => count = count — x;
 const getCount = () => console.log(count);
 return {
 increment,
 decrement,
 getCount
 }
}

В этом случае мы определяем counter как функцию, а не как литерал объекта. Сначала внутри функции определяется переменная count. Это личное для counter function. Тогда increment, decrement и getCount определены как функции внутри функции счетчика. У них есть доступ к переменной count, потому что они также объявлены в функции счетчика. Наконец, increment, decrement и getCount возвращаются из counter function. Из-за этого мы можем вызывать их и косвенно обращаться к переменной count за пределами counter function следующим образом:

let myCounter = counter();
myCounter.increment();
myCounter.increment(5);
myCounter.increment();
myCounter.increment(11);
myCounter.increment();
myCounter.decrement(14);
myCounter.getCount();

Как и в предыдущем примере, в консоли регистрируется 5. Однако, если мы попытаемся получить доступ к счетчику напрямую:

myCounter.count++;
console.log(myCounter.count);

Это не работает. «NaN» регистрируется в консоли. Если мы запустим:

myCounter.getCount();

Мы видим, что count не изменился; это все еще 5.

Выражение немедленно вызываемой функции (IIFE)

Проблема с предыдущим примером заключается в том, что он создает больше переменных, чем необходимо. Оба counter и myCounter являются глобальными переменными. Если программе нужен только один счетчик, мы можем исключить одну из этих переменных с помощью IIFE.

const counter = (function() {
 let count = 0;
 const increment = (x = 1) => count = count + x;
 const decrement = (x = 1) => count = count — x;
 const getCount = () => console.log(count);
 return {
 increment,
 decrement,
 getCount
 }
})();

В этом примере counter - единственная глобальная переменная.

counter.increment();
counter.increment(5);
counter.increment();
counter.increment(11);
counter.increment();
counter.decrement(14);
counter.getCount();

Как и в предыдущих примерах, в консоли регистрируется 5.

Модули ES6

Другой способ скрыть переменные в современном JavaScript - использовать модули ES6. Создайте файл counter.js со следующим содержимым:

let count = 0;
export const increment = (x = 1) => (count = count + x);
export const decrement = (x = 1) => (count = count — x);
export const getCount = () => console.log(count);

Импортируйте экспортированные функции из модуля counter.js в основной модуль вашего проекта (в данном случае index.js) следующим образом:

import * as counter from ‘./counter.js’;

Затем вызовите функции из импортированного модуля counter.js в index.js module:

counter.increment();
counter.increment(5);
counter.increment();
counter.increment(11);
counter.increment();
counter.decrement(14);
counter.getCount();

Наконец, импортируйте файл index.js на свою html-страницу с помощью тега <script>:

<!DOCTYPE html>
<head>
 <title>Counter</title>
</head>
<body>
 <script type=”module” src=”./index.js”></script>
</body>
</html>

Когда вы загрузите html-файл в браузер и посмотрите на консоль, вы увидите, что значение 5 было зарегистрировано. В модуле index.js у вас нет прямого доступа к переменной count в модуле count.js, но у вас есть косвенный доступ через функции increment, decrement и getCount, которые были импортированы из модуля count.js.

Вывод

Эта краткая статья не исчерпывает всего, что можно сказать о сокрытии переменных и закрытии в JavaScript. Более подробное объяснение включало бы обсуждение контекста выполнения, лексической среды и цепочки областей видимости, как вы найдете в этой статье. Я считаю, что достиг своей цели, которая заключалась в том, чтобы представить базовое понимание того, как скрывать переменные и что такое закрытие в JavaScript.

Другие статьи из этой серии: