Недавно я прошел телефонное собеседование, и мне задали несколько вопросов, на которые я мог бы ответить лучше. Цель этой серии сообщений в блоге - помочь мне запомнить эти концепции, поэтому в следующий раз у меня будет лучший ответ. Если другие сочтут эти сообщения полезными, тем лучше.
Скрытие переменных
Как разработчик, бывают случаи, когда вы хотите контролировать, как другие разработчики взаимодействуют с вашим кодом. Возможно, вы захотите убедиться, что определенные переменные в вашем коде изменяются только контролируемым образом. Взгляните на следующий пример:
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.
Другие статьи из этой серии: