7 вопросов для собеседования о замыкание в JavaScript. Сможете ли вы на них ответить?
Перевод: Dmitri Pavlutin — 7 Interview Questions on JavaScript Closures. Can You Answer Them?
Каждый разработчик 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: найти замыкание
- 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.
Вопрос 4: правильный ответ 2 а не 3
Будет 333, т.к. счетчик все равно увеличится, но цикл уже не пойдет.
Перед тем как что-то писать, сначала обдумай это, а лучше проверь в консоли. Только новичков в заблуждение вводишь, «знаток».
Дополнительный вопрос: как бы вы исправили функцию log (), чтобы она возвращала сообщение, имеющее фактическое значение счетчика? Напишите свое решение в комментарии ниже!
Логичнее message поместить в функцию log.
Тогда при первом вызове log(), без вызова increment(), у нас будет просто ‘0’, а не ‘Count is 0’
Хочу указать на пару ошибок в переводе, из-за чего некоторые вопросы и ответы не являются не совсем корректными:
Первый вопрос: Which of these 3 functions access outer scope variables — подразумевалось не «какая из трех функций», а «какие их трех функций» (поскольку правильных ответов больше одного).
В ответе на этот же вопрос: внешняя область — это outer scope, а глобальная область — outermost scope, что совсем не то же самое что и outer (правильнее было бы назвать ее крайней, а лучше — вообще опустить этот уточнение.