В подборку попали типичные вопросы об отладке, с которыми сталкиваются на собеседованиях разработчики на JavaScript. Попытайтесь ответить на них самостоятельно, а потом сравните ответ с правильным решением.
- Вопрос 1: объекты и ссылки на объекты
- Вопрос 2: область видимости и event loop
- Вопрос 3: контекст функции
- Вопрос 4: прототипы и ключевое слово class
- Вопрос 5: строгое равенство
- Вместо заключения
Вопрос 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:
- Текущий стек вызовов установлен для первого
setTimeout()
. window.setTimeout()
рассматривается в качестве веб API для выполнения неблокирующих операций ввода-вывода (I/O). Стек вызовов отправляет этот код в веб API. Через 0 миллисекунд колбэк, в данном случае — анонимная функция, отправляется в очередь (queue), но не в стек вызовов.- Поскольку стек вызовов свободен, цикл for переходит ко второму
setTimeout()
и работает, пока верно условиеi < 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.