Собеседования

Какие вопросы об отладке часто задают на собеседовании JS-разработчикам: примеры и объяснения

Какие вопросы об отладке часто задают на собеседовании JS-разработчикам: прим... главное изображение

В подборку попали типичные вопросы об отладке, с которыми сталкиваются на собеседованиях разработчики на JavaScript. Попытайтесь ответить на них самостоятельно, а потом сравните ответ с правильным решением.

Вопрос 1: объекты и ссылки на объекты

Почему вместо hey amy получаем результат hey arnold?

const greet = (person) => {
 if (person === { name: 'amy' }) {
   return 'hey amy';
 }
 return 'hey arnold';
};
greet({ name: 'amy' });

Ответ

Вот секрет: { name: 'amy' } !== { name: 'amy' }. Когда JavaScript проверяет равенство или строгое равенство объектов, он использует ссылки на объект. В данном случае мы видим объекты с одинаковыми свойствами. Но для JavaScript это два разных объекта.

Чтобы получить ожидаемый результат, нужно использовать такой код:

const greet = (person) => {
 if (person.name === 'amy') {
   return 'hey amy';
 }
 return 'hey arnold';
};
greet({ name: 'amy' });

Вопрос 2: область видимости и event loop

Почему код из примера ниже не выводит значения с 0 до 3 по порядку?

for (var i = 0; i < 4; i += 1) {
 setTimeout(() => console.log(i), 0);
}

Проблема

Это неочевидный вопрос. Чтобы правильно ответить на него, нужно понимать область видимости и цикл событий (event loop) в JavaScript.

Проблема прячется в нулевой задержке. setTimeout(() => console.log(i), 0) не значит, что колбэк выполнится через 0 миллисекунд. Посмотрим на код через призму event loop:

  1. Текущий стек вызовов установлен для первого setTimeout().
  2. window.setTimeout() рассматривается в качестве веб API для выполнения неблокирующих операций ввода-вывода (I/O). Стек вызовов отправляет этот код в веб API. Через 0 миллисекунд колбэк, в данном случае — анонимная функция, отправляется в очередь (queue), но не в стек вызовов.
  3. Поскольку стек вызовов свободен, цикл for переходит ко второму setTimeout() и работает, пока верно условие i < 4.
  4. Цикл завершается, когда i === 4. Теперь JavaScript выполняет очередь колбэков. Каждый console.log(i) выводит 4.

Ещё одна проблема связана с областью видимости. В примере выше четыре экземпляра setTimeout() работают с одним экземпляром i. Это хорошо иллюстрирует код из примера ниже.

let foo = 'hop!';
const getFoo = () => foo;
foo = 'hey!';
getFoo(); // 'hey!'

Ответ

Задачу можно решить несколькими способами. Первый — используйте функцию-обёртку для setTimeout().

for (var i = 0; i < 4; i++) {
 (function (i) {
   setTimeout(() => console.log(i), 0)
 })(i)
}

Второй способ — просто используйте ключевое слово let вместо var. Переменная, объявленная с помощью var, сохраняется при каждом повторении цикла. А при использовании let при каждом повторении цикла создаётся новая переменная.

for (let i = 0; i < 4; i += 1) {
 setTimeout(() => console.log(i), 0);
}

Вопрос 3: контекст функции

Дан код:

const dog = {
 name: 'Guffi',
 sayName() {
   console.log(this.name);
 },
};
const sayName = dog.sayName;

sayName();

Почему получаем результат undefined?

Ответ

В коде выше определяется объект dog, в котором два свойства. Мы копируем свойство объекта в константу sayName, а затем вызываем sayName в глобальном контексте. Функция sayName() возвращает window.name, так как в Node.js вызов происходит в глобальном контексте, где typeof window.name === 'undefined'.

Проблему можно решить двумя способами: удобным и не очень. Сначала не очень удобный подход: привязываем sayName к контексту dog с помощью bind.

const dog = {
 name: 'Guffi',
 sayName() {
   console.log(this.name);
 },
};
dog.sayName.bind(dog)();

Более удобный подход: сразу вызываем функцию в нужном контексте.

const dog = {
 name: 'Guffi',
 sayName() {
   console.log(this.name);
 },
};
dog.sayName();

Вопрос 4: прототипы и ключевое слово class

Дан код:

function Dog (name) {
 this.name = name;
}
Dog.bark = function () {
 console.log(this.name + ' says woof');
}
const fido = new Dog('fido');
fido.bark();

Почему собака по кличке fido не лает?

Ответ

В выводе появляется ошибка: TypeError: fido.bark is not a function. Есть несколько решений задачи. Вот не самый удобный подход:

function Dog (name) {
 this.name = name;
}
Dog.bark = function () {
 console.log(this.name + ' says woof');
}
const fido = new Dog('fido');
const boundedBark = Dog.bark.bind(fido);
boundedBark();

fido.bark — не функция, но у нас есть функция Dog.bark. Поэтому в примере выше решаем задачу с помощью function.prototype.bind(). Но использование function.prototype.bind() часто приводит к проблемам, поэтому удобнее определить bark() в прототипе Dog:

function Dog (name) {
 this.name = name;
}

Dog.prototype.bark = function () {
 console.log(this.name + ' says woof');
};

const fido = new Dog('fido');
fido.bark();

И самый удобный вариант: использовать синтаксис ES2015, в частности, ключевое слово class.

class Dog {
 constructor(name) {
   this.name = name;
 }

 bark() {
   console.log(`${this.name} says woof`);
 }
}

const fido = new Dog('fido');
fido.bark();

Вопрос 5: строгое равенство

Почему код из примера ниже ведёт себя не так, как мы ожидаем?

const isBig = (thing) => {
 if (thing == 0 || thing == 1 || thing == 2) {
   return false;
 }
 return true;
};
isBig(1); // false
isBig([2]); // false
isBig([3]); // true

Ответ

В коде используется оператор сравнения ==. Этот оператор позволяет сравнивать разные типы данных. Вот что происходит.

С аргументом 1 функция isBig() работает как ожидается. При вызове isBig([2]) срабатывает условие thing == 2. При сравнении массива и числа JavaScript преобразует массив в число. Так работает алгоритм сравнения абстрактного равенства: когда мы сравниваем число и объект, а массивы в JS — это объекты, объект преобразуется в число. В нашем массиве один элемент, поэтому [2] == 2.

Неявные преобразования сложно отслеживать, поэтому при использовании оператора == возможны сюрпризы. Рекомендуется использовать оператор сравнения ===.

Вместо заключения

У нас есть репозиторий с реальными тестовыми заданиями от разных компаний. Используйте его, чтобы оценить свой уровень и прокачаться перед собеседованиями. А если у вас остались вопросы по задачам для JavaScript-разработчиков, пишите в комментариях.

Адаптированный перевод статьи Typical JavaScript interview exercises (explained) by Maxence Poutord.

Аватар пользователя Дмитрий Дементий
Дмитрий Дементий 13 января 2020