10 наиболее распространенных вопросов на собеседовании по JavaScript

Проходить JavaScript интервью всегда непростая задача. Вопросы могут быть из самых разных областей, в том числе включать в себя задачи из новой или старой версии языка. Особенности браузеров, работы с HTML, приемы и хитрости связанные с jQuery и т.д. Само собой разумеется, вопросы будут касаться и алгоритмических проблем, работы с данными, возможно даже спросят про некоторые фреймворки.

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

1. Какая разница между объявлением переменной через let и var?

Во-первых, var существует в языке с самого начала. Let появился в ES2015/ES6. Во-вторых, область видимости let ограничивается текущим блоком. Это означает, что переменная, объявленная таким образом, будет уничтожена после выполнения кода текущего блока. Var имеет область видимости, ограниченную функцией. То есть переменная будет уничтожена после выполнения текущей функции, а не блока. В-третьих, var «поднимается» в начало функции, в отличие от let. Имеется ввиду инициализация, а не присваивание значения:

  1. let x = function() {
  2. if (true) {
  3. console.log(v); //undefined
  4. console.log(q); //Uncaught ReferenceError: q is not defined
  5. var v = 2;
  6. let q = 1;
  7. //q уничтожается здесь
  8. }
  9. //v уничтожается здесь
  10. }
  11. x();

Переменная q существует ровно до закрывающей фигурной скобки блока if. В то время как v будет «жить» до конца функции. Вероятность что вам зададут этот вопрос – 20%

2. Объясните разницу между «==» и «===»?

Двойной знак равенства и тройной – оба служат для сравнения. Первый сравнивает только значения, без учета их типа. Тройной сравнивает и значение и тип.

  1. if (1== 1) //true
  2. if (1=== 1) //false

Рассмотрим, как работает сравнение в первом случае. Для того, чтобы определить равны ли значения, JavaScript сначала приводит их к одному и тому же типу. Слева, как мы видим, у нас строка. Справа – целое число. В данном случае, именно целое число будет приведено к строке, затем, сравнивая строки друг с другом, получим true.

Когда используется тройной знак равенства, приведение к типу не происходит, а сначала сравниваются типы (переменных, например), и только если они совпадают, сравниваются их значения. Вероятность вопроса на собеседовании – 30%

3. Разница между объявлением let и const?

Оба варианта используются для объявления переменных. Но const объявляет неизменяемую переменную, которой нельзя потом присвоить другое значение. Для сравнения значение let, можно переопределять столько раз, сколько потребуется. Вы можете даже изменить тип, если хотите.

  1. let q = 1;
  2. q = 2;
  3. concole.log(q); //2
  4. const c = 1;
  5. c = 2; //Uncaught TypeError: Assignment to constant variable.
  6. console.log(c);
  7. </pre>
  8. Более интересный вариант:
  9. <p>c1a214af3092987b0a5b48232c024929000</p>
  10. получим ошибку: Uncaught SyntaxError: Missing initializer in const declaration.
  11. Еще один:
  12. <pre lang=«js»>
  13. const c = [1,2];
  14. c.push(3);
  15. console.log(c); //(3) [1,2,3]

Ошибки нет! Мы можем изменять значения таким образом, потому что это объект. Но мы не можем переприсвоить, то есть, вместо c.push(3) написать c = [1,2,3].

4. Разница между «undefined» и «null»?

Ответ: оба варианта означают пустое значение. Если мы инициализируем переменную, но не присваиваем ей значение, туда помещается специальный «маркер», который отображается при выводе на экран как undefined. Null присваиваем самостоятельно. Если необходимо очистить значение переменной, мы делаем q = null.
Во-вторых:
typeof(undefined) => undefined
typeof(null) => object
С вероятностью 10% вам зададут данный вопрос.

5. Как использовать «стрелочные функции» (Arrow Functions)?

  1. const profile = {
  2. firstName: ‘’,
  3. lastName: ‘’,
  4. setName: function(name) {
  5. let splitName = function(n) {
  6. let nameArray = n.split(‘ ‘);
  7. this.firstName = nameArray[0];
  8. this.lastName = nameArray[1];
  9. }
  10. splitName(name);
  11. }
  12. }
  13. profile.setName(‘John Doe’);
  14. console.log(profile.firstName);

Результат будет пустым. Поскольку this в функции splitName указывает на объект window, а не на profile.
console.log(window.firstName); //John
Чтобы подобного не происходило, там следует использовать Narrow Function. Заменим часть кода, где объявляется splitName:

  1. let splitName = (n) => {
  2. let nameArray = n.split(‘ ‘);
  3. this.firstName = nameArray[0];
  4. this.lastName = nameArray[1];
  5. }

Теперь выполним console.log(profile.firstName); //John
Это и есть «стрелочная функция». Кстати, они появились с ES6. Поскольку у нее нет собственного this, контекст берется из setName, который принадлежит объекту profile. Вопрос будет с вероятностью 30%.

6. Что такое прототипное наследование (prototypal inheritance)

Грамотным ответом будет: изначально каждый объект обладает свойством — прототипом. Вы можете добавлять в него методы и свойства. Создавать другие объекты на основе прототипа. Создаваемый объект автоматически унаследует свойства и своего прототипа. Если свойство в новом объекте отсутствует, то будет произведен его поиск в прототипе.

  1. let car = function(model) {
  2. this.model = model;
  3. };
  4. car.prototype.getModel = function(){
  5. return this.model;
  6. }
  7. let honda = new car(‘honda’);
  8. console.log(honda.getModel()); //honda
  9. let bmw = new car(‘bmw’);
  10. console.log(bmw.getModel()); //bmw

Функция car называется конструктором. Далее мы добавляем метод getModel в ее прототип, с помощью специальной конструкции car.prototype.getModel(). Если вы не поняли о чем здесь говорится, настоятельно рекомендую ознакомиться с прототипным ООП: https://learn.javascript.ru/prototypes
30% вам зададут этот или похожий вопрос.

7. Разница между объявлением функции (function declaration) и функциональным выражением (function expression)?

  1. function funcA(){
  2. console.log(‘function declaration’);
  3. };
  4. var funcB = function() {
  5. console.log(‘function expression’);
  6. }

Как видим, функциональное выражение, — это переменная, которой присваивается функция.
Важная особенность заключается в том, что функция объявленная через function declaration доступна по тексту программы до ее описания. В то время как функциональное выражение ведет себя как переменная, таким образом, к функции можно обратиться только после. Если мы в предыдущем примере, в самом начале допишем две строки:

  1. console.log(funcA()); //function declaration
  2. concole.log(funcB()); //Uncaught ReferenceError: funcB is not defined.

Кстати, вместо var можно использовать let, со всеми вытекающими последствиями, касающимися области видимости.
Если вы хотите передать функцию в качестве аргумента другой функции, то вам необходимо воспользоваться функциональным выражением. Потому что это переменная. То есть передать переменную в другую функцию.

8. Что такое промисы (promises) и для чего их используют.

Ответить на данный вопрос иногда трудно, но вы можете просто сказать, что с помощью них можно выполнять действия (функции, методы) в асинхронном режиме. Добиться асинхронности можно и с помощью колбэков, но в случае большой вложенности (большого количества асинхронных действий), мы получим плохо читаемый код.

  1. $.ajax({
  2. url: «test.json»,
  3. success: function(r) {
  4. $.ajax({
  5. url: «baz.json?» + r.test,
  6. success: function(result) {
  7. $(«#div1»).html(result);
  8. }
  9. });
  10. }
  11. });

Видите, насколько сильно ветвится данный код? Теперь, напишем тоже самое с использованием промисов (схематично):

  1. var p1 = new Promise(function(resolve, reject) {
  2. resolve($.ajax(‘test.json’));
  3. });
  4. p1.then(function(r){
  5. return new Promise();
  6. }).then(function(result){
  7. $(“#div1”).html(result);
  8. });

9. Вопрос про setTimeout()

Скорее всего, у вас не будут спрашивать, что же это такое, поскольку ответ на подобный вопрос слишком очевиден. Вместо этого предложат фрагмент кода, и вы должны будете ответить, каков результат его выполнения.

  1. console.log(‘a’);
  2. console.log(‘b’);
  3. console.log(‘c’);
  4. //увидим последовательный вывод a затем b и с.
  5. </pre>
  6. Теперь сделаем тоже самое, только вывод ‘а’ обернем в setTimeout().
  7. <pre lang=«js»>
  8. setTimeout(function() {
  9. console.log(‘a’);
  10. }, 0);

По логике вещей, поскольку таймаут равен 0, мы можем предположить, что последовательность вывода останется такой же. Однако на самом деле, мы увидим сначала b, потом c и только в конце – a.
Так происходит потому, что когда мы использует setTimeout, код, который в него заключен, становится асинхронным. JS выполнит его после того, как освободится стэк. То есть после b и c.

10. Замыкания (closure) и как их использовать

Наверняка вам зададут вопрос на собеседовании.

Когда одна функция возвращает другую функцию (или объект), последняя содержит окружение своего как бы «родителя». Обратимся к примеру:

  1. let bar = function() {
  2. let i = 0;
  3. return {
  4. setI(b) {
  5. i = k;
  6. },
  7. getI() {
  8. return i;
  9. }
  10. }
  11. };
  12. let x = bar();
  13. x.setI(2);
  14. console.log(x.getI()); //2

Напоследок, хотелось бы посоветовать не спешить с ответами при прохождении собеседования, особенно на вопросы, связанные с анализом предложенного кода. Часто очевидные на первый взгляд вопросы содержат хитрости и ловушки. Остановитесь, подумайте лишнюю минутку. Задайте уточняющие вопросы — это продемонстрирует вашу внимательность и ответственное отношение к делу. Поможет выявить интервьюверу ход ваших мыслей. Собственно, в основном для этого и задаются хитрые или сложные задачи. Гораздо важнее продемонстрировать правильное мышление, даже если ответ на вопрос в итоге будет неверным.

Человек не может знать всё, это в принципе невозможно. Часто бывает так, что на прошлой работе вы сталкивались с одними технологиями и приёмами, а на новой команда имеет другие взгляды на архитектуру и пользуется не очень знакомыми вам библиотеками. Вот почему грамотный интервьювер в первую очередь постарается выяснить, что за человек перед ним, как он мыслит, его аналитические способности. В конце концов, недостающие пробелы заполнить очень легко, чего нельзя сказать о правильном мышлении программиста, которое формируется только с опытом.