Каждый разработчик JavaScript должен знать, что такое замыкание. Практически на каждом собеседования на позицию веб разработчика сплывает вопрос о концепции замыкания.
Я составил список из 7 интересных и более менее сложных вопросов о замыкание в JavaScript.
Возьмите карандаш и лист бумаги и попробуйте ответить на эти вопросы, не глядя на ответы и не запуская код. По моим оценкам, вам потребуется около 30 минут. Ответы буду в конце статьи.
Have fun!
Если вам нужно обновить знания о концепции замыкания, я рекомендую прочесть A Simple Explanation of JavaScript Closures.
Вопрос 1: найти замыкание
Рассмотрим следующие функции clickHandler, inventory и delayedReload:
let countClicks = 0;
button.addEventListener(‘click’, function clickHandler() {
countClicks++;
});
const result = (function immediate(number) {
const message = `number is: ${number}`;
return message;
})(100);
setTimeout(function delayedReload() {
location.reload();
}, 1000);
Какая из этих трех функций является замыканием и использует переменные из внешней области видимости?
Вопрос 2: потеряться в параметрах
Что будет выведено в консоль, в следующем фрагменте кода:
(function immediateA(a) {
return (function immediateB(b) {
console.log(a); // What is logged?
})(1);
})(0);
Вопрос 3: кто есть кто
Что будет выведено в консоль, в следующем фрагменте кода:
let count = 0;
(function immediate() {
if (count === 0) {
let count = 1;
console.log(count); // What is logged?
}
console.log(count); // What is logged?
})();
Вопрос 4: хитрое замыкание
Что будет выведено в консоль, в следующем фрагменте кода:
for (var i = 0; i < 3; i++) {
setTimeout(function log() {
console.log(i); // What is logged?
}, 1000);
}
Вопрос 5: правильное или неправильное сообщение
Что будет выведено в консоль, в следующем фрагменте кода:
function createIncrement() {
let count = 0;
function increment() {
count++;
}
let message = `Count is ${count}`;
function log() {
console.log(message);
}
return [increment, log];
}
const [increment, log] = createIncrement();
increment();
increment();
increment();
log(); // What is logged?
Вопрос 6: восстановить инкапсуляцию
Следующая функция createStack () создает структуру данных типа стек:
function createStack() {
return {
items: [],
push(item) {
this.items.push(item);
},
pop() {
return this.items.pop();
}
};
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => [10]
stack.items = [10, 100, 1000]; // Encapsulation broken!
Стек работает должным образом, но с одной небольшой проблемой. Любой может изменить массив элементов напрямую, потому что свойство stack.items открыто для изменения.
Это проблема, поскольку это нарушает инкапсуляцию: так как только методы push () и pop () должны быть общедоступными, а stack.items не должен быть доступен снаружи.
Выполните рефакторинг приведенной выше реализации стека, используя концепцию замыкания, чтобы не было возможности получить доступ к массиву элементов stack.items за пределами области действия функции createStack ():
function createStack() {
// Write your code here…
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => undefined
Вопрос 7: умное умножение
Напишите функцию multiply (), которая умножает 2 числа:
function multiply(num1, num2) {
// Write your code here…
}
Если multiply (num1, numb2) вызывается с 2 аргументами, она должна вернуть умножение этих двух аргументов.
Но если вызывается с одни аргументом const anotherFunc = multiply (num1), функция должна возвращать другую функцию. Возвращенная функция при вызове anotherFunc (num2) должна выполнить умножение num1 * num2.
multiply(4, 5); // => 20
multiply(3, 3); // => 9
const double = multiply(2);
double(5); // => 10
double(11); // => 22
Ответы на вопросы
Вопрос 1: найти замыкание
AD
Начни жизнь на одинокой планете
AD
Сериалы, кино и ТВ в хорошем качестве
- clickHandler создает замыкание с переменной countClicks из внешней области.
- immediate не является замыканием, потому что не имеет доступа ни к каким переменным из внешней области.
- delayedReload создает замыкание из-за использования переменной, из глобальной области (также известной как внешняя область).
Вопрос 2: потеряться в параметрах
В консоль выводится 0.
immediateA вызывается с аргументом 0, поэтому параметр равен 0.
Функция immediateB, вложенная в функцию immediateA , и представляет собой замыкание, которое захватывает переменную из внешней области immediateA , где а равно 0. Таким образом, console.log (а) выводит 0.
Вопрос 3: кто есть кто
В консоль выводится 1 и 0.
Первый оператор let count = 0 объявляет переменную count.
immediate() — это замыкание, которое захватывает переменную count из внешней области видимости. Внутри области видимости функции immediate() count равен 0.
Однако внутри условного выражения другое let count = 1 объявляет локальную переменную count, который перезаписывает count из внешней области. Первый console.log (count) выводит 1.
Второй console.log (count) выводит 0, поскольку здесь доступ к переменной count осуществляется из внешней области.
Вопрос 4: хитрое замыкание
В консоль выводится 3, 3, 3 .
Данный фрагмент кода выполняется в 2 этапа.
Этап 1
- for () повторяется 3 раза. Во время каждой итерации создается новая функция log (), которая фиксирует переменную i. setTimout () планирует выполнение log () через 1000 мс.
- Когда цикл for () завершается, переменная i имеет значение 3.
Этап 2
Вторая фаза происходит через 1000 мс:
- setTimeout() выполняет запланированные функции log (). log () считывает текущее значение переменной i, которое на данный момент равно 3, и выводит в консоль 3.
Вот почему в консле 3, 3, 3 регистрируются в консоли.
Дополнительный вопрос: как бы вы исправили этот пример, чтобы записать значения 0, 1, 2?
Вопрос 5: правильное или неправильное сообщение
В консоль выводится 'Count is 0'.
Функция increment () была вызвана 3 раза, увеличивая Count до значения 3.
переменная message существует в рамках функции createIncrement (). Его начальное значение — «Count is 0». Однако, даже если переменная count была увеличена несколько раз, переменная message все еще содержит значение «Count is 0».
Функция log () — это замыкание, которое захватывает переменную message из области createIncrement (). В итоге console.log (message) выводит «Count is 0».
Дополнительный вопрос: как бы вы исправили функцию log (), чтобы она возвращала сообщение, имеющее фактическое значение счетчика? Напишите свое решение в комментарии ниже!
Вопрос 6: восстановить инкапсуляцию
Вот возможный рефакторинг createStack ():
function createStack() {
const items = [];
return {
push(item) {
items.push(item);
},
pop() {
return items.pop();
}
};
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => undefined
Переменную items переместили в внутрь области createStack ().
Благодаря этому изменению вне области createStack () нет возможности получить доступ или изменить массив items. Теперь items является приватной переменной: общедоступными являются только методы push () и pop ().
Методы push () и pop (), являясь замыканием, захватывают переменную элементов из области действия функции createStack ().
Вопрос 7: умное умножение
Вот возможная реализация функции multiply ():
function multiply(number1, number2) {
if (number2 !== undefined) {
return number1 * number2;
}
return function doMultiply(number2) {
return number1 * number2;
};
}
multiply(4, 5); // => 20
multiply(3, 3); // => 9
const double = multiply(2);
double(5); // => 10
double(11); // => 22
Если параметр number2 не является undefined, функция просто возвращает number1 * number2.
Но если number2 undefined, это означает, что функция multiply () была вызвана с одним аргументом. В таком случае давайте вернем функцию doMultiply (), которая при последующем вызове выполняет фактическое умножение.
doMultiply () — это замыкание, потому что оно захватывает переменную number1 из области видимости multiply ().
Заключение
Сравните свои ответы с ответами в статье:
- Вы хорошо понимаете замыкание, если правильно ответили на 5 или более вопросов.
- Но если вы правильно ответили менее чем на 5 вопросов, вам нужно освежить знание о замыкание. Рекомендую почитать мой пост: A Simple Explanation of JavaScript Closures.