JavaScript

7 вопросов для собеседования о замыкание в JavaScript. Сможете ли вы на них ответить?

Spread the love

Перевод: Dmitri Pavlutin7 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: найти замыкание

  1. clickHandler создает замыкание с переменной countClicks из внешней области.
  2. immediate не является замыканием, потому что не имеет доступа ни к каким переменным из внешней области.
  3. 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

  1. for () повторяется 3 раза. Во время каждой итерации создается новая функция log (), которая фиксирует переменную i. setTimout () планирует выполнение log () через 1000 мс.
  2. Когда цикл for () завершается, переменная i имеет значение 3.

Этап 2

Вторая фаза происходит через 1000 мс:

  1. 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.
Была ли вам полезна эта статья?
[8 / 3.5]

Spread the love
Editorial Team

View Comments

    • Будет 333, т.к. счетчик все равно увеличится, но цикл уже не пойдет.

    • Перед тем как что-то писать, сначала обдумай это, а лучше проверь в консоли. Только новичков в заблуждение вводишь, "знаток".

  • Дополнительный вопрос: как бы вы исправили функцию log (), чтобы она возвращала сообщение, имеющее фактическое значение счетчика? Напишите свое решение в комментарии ниже!

     function createIncrement() {
      let count = 0;
      let message;
      function increment() {
        count++;
        message = Count is ${count};
      }
    
    
      function log() {
        console.log(message);
      }
    
    
    
      return [increment, log];
    }
    
    
    let [increment, log] = createIncrement();
    increment();
    increment();
    increment();
    
    
    log(); // // Count is 3
    
    • Логичнее message поместить в функцию log.

    • Тогда при первом вызове log(), без вызова increment(), у нас будет просто '0', а не 'Count is 0'

  • Хочу указать на пару ошибок в переводе, из-за чего некоторые вопросы и ответы не являются не совсем корректными:
    Первый вопрос: Which of these 3 functions access outer scope variables - подразумевалось не "какая из трех функций", а "какие их трех функций" (поскольку правильных ответов больше одного).
    В ответе на этот же вопрос: внешняя область - это outer scope, а глобальная область - outermost scope, что совсем не то же самое что и outer (правильнее было бы назвать ее крайней, а лучше - вообще опустить этот уточнение.

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

11 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

11 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago