Привет, Хабр. Для будущих студентов курса "JavaScript Developer. Professional" подготовили перевод интересного материала.
В рамках набора студентов на курс приглашаем принять участие в открытом карьерном вебинаре, где преподаватель расскажет о себе, своём профессиональном опыте и ответит на вопросы касательно карьеры в этой области.
7 Вопросов из интервью по замыканиям (closures) в JavaScript. Можете ли вы ответить на них?
Каждый разработчик JavaScript должен знать, что такое замыкание (closure). Во время собеседования по кодированию JavaScript есть большая вероятность, что вас спросят о концепции замыкания.
Я составил список из 7 интересных и наиболее сложных вопросов по замыканиям в JavaScript.
Возьмите карандаш, лист бумаги и постарайтесь ответить на вопросы, не глядя на ответы и не запуская код. По моим подсчетам, вам понадобится около 30 минут.
Развлекайтесь!
Если вам нужно освежить свои знания о замыкании, я рекомендую ознакомится с публикацией A Simple Explanation of JavaScript Closures (Простое объяснение замыканиям в JavaScript).
Содержание
Вопрос 1: Замыкания развязывают твои руки
Вопрос 2: Утерянные параметры
Вопрос 3: Кто есть кто
Вопрос 4: Хитроумное замыкание
Вопрос 5: Правильное или неправильное сообщение
Вопрос 6: Восстановление инкапсуляции (Restore encapsulation)
Вопрос 7: Умное перемножение
Резюме
Вопрос 1: Замыкания развязывают твои руки
Рассмотрим следующие функции: clickHandler
, immediate
, и delayReload
:
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);
Какие из этих 3 функций получают доступ к переменным внешней области видимости (outer scope)?
Расширенный ответ
clickHandler
обращается к переменнойcountClicks
из внешней области видимости.immediate
не обращается ни к каким переменным из внешней области видимости.delayReload
обращается к глобальной переменнойlocation
из глобальной области видимости (так же известной как крайняя область видимости (outermost scope)).
Вопрос 2: Утерянные параметры
Что будет записано в консоль для следующего фрагмента кода (code snippet):
(function immediateA(a) {
return (function immediateB(b) {
console.log(a); // What is logged?
})(1);
})(0);
Расширенный ответ:
0
записывается на консоль. Посмотрите демо.
immediateA
вызывается с аргументом 0
, таким образом, параметр равен 0
.
Функция immediateB
, будучи вложенной в функцию immediateA
, является замыканием, которое захватывает переменную a из внешней области видимости immediateA
, где a
равно 0
. Таким образом, console.log(a)
записывает в журнал 0
.
Вопросы 3: Кто есть кто
Что будет записано в консоль для следующего фрагмента кода (code snippet):
let count = 0;
(function immediate() {
if (count === 0) {
let count = 1;
console.log(count); // What is logged?
}
console.log(count); // What is logged?
})();
Расширенный ответ:
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: Хитроумное замыкание
Что будет записано в консоль в следующем фрагменте кода (code snippet):
for (var i = 0; i < 3; i++) {
setTimeout(function log() {
console.log(i); // What is logged?
}, 1000);
}
Расширенный ответ:
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: Правильное или неправильное сообщение
Что будет записано в консоль в следующем фрагменте кода (code snippet):
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?
Расширенный ответ:
'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(), чтобы она возвращала сообщение, имеющее фактическое значение count? Запишите ваше решение в комментариях ниже!
Вопрос 6: Восстановление инкапсуляции (Restore encapsulation)
Следующая функция 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
или любые другие элементы не должны быть доступны.
Рефакторизуйте описанную выше реализацию стека, используя концепцию замыкания, так, чтобы не было возможности доступа к массиву items
вне области видимости функции createStack()
:
function createStack() {
// Write your code here...
}
const stack = createStack();
stack.push(10);
stack.push(5);
stack.pop(); // => 5
stack.items; // => undefined
Расширенный ответ:
Вот возможный рефакторинг (refactoring) функции 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
или изменить его. Элементы сейчас являются приватной переменной, а стек инкапсулирован: только методы push()
и pop()
являются публичными.
Методы push()
и pop()
, будучи замкнутыми, захватывают переменную items
из области видимости функции createStack()
.
Вопрос 7: Умное перемножение
Напишите функцию multiply()
, которая умножает 2 числа:
function multiply(num1, num2) {
// Write your code here...
}
Если multiply(num1, numb2)
будет вызвана с 2 аргументами, то она должна вернуть умножение 2 аргументов.
Но в том случае, если вызывается 1 аргумент const anotherFunc = multiply(numb1)
, то функция должна возвращать другую функцию. Возвращаемая функция при вызове anotherFunc(num2)
выполняет умножение num1 * num2
.
multiply(4, 5); // => 20
multiply(3, 3); // => 9
const double = multiply(2);
double(5); // => 10
double(11); // => 22
Расширенный ответ:
Вот возможная имплементация функции 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 (Простое объяснение замыкания в JavaScript).
Готовы к новому испытанию? Попробуйте ответить на 7 вопросов в интервью по ключевому слову "this" в JavaScript.
Узнать подробнее о курсе "JavaScript Developer. Professional"
Смотреть открытый карьерный вебинар