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

JavaScript опрос

1. Каковы потенциальные подводные камни использования typeof bar === "object" для определения того, является ли bar объектом? Как избежать этой ловушки?

В то время как typeof bar === "object" является надежным способом проверить, является ли bar объектом, неожиданная проблема в JavaScript заключается в следующем.nullТакже считается объектом!

Таким образом, для большинства разработчиков приведенный ниже код будет отображать в консоли значение true (не false):

var bar = null;
console.log(typeof bar === "object");  // logs true!

Зная это, вы можете легко избежать проблемы, проверив, пуст ли бар:

console.log((bar !== null) && (typeof bar === "object"));  // logs false

Чтобы наш ответ был более полным, стоит отметить еще две вещи:

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

console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));

Во-вторых, приведенное выше решение вернет true, если bar является массивом (например, если var bar = [];). В большинстве случаев это желаемое поведение, поскольку массивы действительно являются объектами, но в случае, когда вы хотите, чтобы значение false было и для массивов, приведенное выше решение можно изменить на:

console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));

Однако есть альтернатива, которая возвращает false для нулей, массивов и функций, но true для объектов:

console.log((bar !== null) && (bar.constructor === Object));

Или, если вы используете jQuery:

console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));

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

console.log(Array.isArray(bar));

2. Что следующий код выведет на консоль и почему?

(function(){
  var a = b = 3;
})();

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

Поскольку и a, и b определены в объемлющей области видимости функции и поскольку они находятся в строках, начинающихся с ключевого слова var, большинство разработчиков JavaScript ожидают, что и typeof a, и typeof b в приведенном выше примере не определены.

Тем не менее, это не так. Проблема здесь в том, что большинство разработчиков неправильно понимают оператор var a = b = 3; следующий сокращенно выглядит так:

var b = 3;
var a = b;

Но на самом деле var a = b = 3; на самом деле является сокращением:

b = 3;
var a = b;

Итак (если вы не используете строгий режим), вывод фрагмента кода будет таким:

a defined? false
b defined? true

Но как определить b вне области объемлющей функции? Что ж, поскольку оператор var a = b = 3; является сокращением для оператора b = 3; а var a = b; b становится глобальной переменной (поскольку она не стоит после ключевого слова var), она все еще находится в области видимости, даже после вне объемлющей функции.

Обратите внимание, что в строгом режиме (т. е. с использованиемstrict), инструкция var a = b = 3; выдает ошибку времени выполнения ReferenceError: b не определена, что позволяет избежать подделок/ошибок, которые могут возникнуть. (Вот почему вы должны использовать strict в своем коде, важный пример!)

3. Что следующий код выведет на консоль? ,Зачем?

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

Приведенный выше код выведет на консоль:

outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

Во внешней функции и this, и self ссылаются на myObject, поэтому оба могут правильно ссылаться на foo и обращаться к нему.

Но во внутренней функции this больше не указывает на myObject. Таким образом, this.foo не определен во внутренней функции, в то время как ссылка на локальную переменную self все еще находится в области видимости и доступна там.

4. Какова важность и причина инкапсуляции всего содержимого исходного файла JavaScript в функциональный блок?

Это становится все более распространенной практикой, используемой многими популярными библиотеками JavaScript (jQuery, Node.js и т. д.). Этот метод создает закрытие всего содержимого файла, что, вероятно, наиболее важно, создает частное пространство имен, что помогает избежать потенциальных конфликтов имен между различными модулями и библиотеками JavaScript.

Другой особенностью этого метода является присвоение глобальным переменным легкодоступного (возможно, более короткого) псевдонима. Например, это часто используется в плагинах jQuery. jQuery позволяет вам использовать jQuery.noConflict() для отключения $refs в пространстве имен jQuery. Если вы это сделаете, ваш код все еще может использовать метод закрытия $, например:

(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

5. В чем смысл и польза включения «use strict» в начале исходного файла JavaScript?

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

Некоторые из основных преимуществ строгого режима включают в себя:

  • Облегчает отладку.Ошибки в коде, которые в противном случае были бы проигнорированы или завершились ошибкой, теперь будут генерировать ошибки или вызывать исключения, что позволяет быстрее обнаруживать проблемы в коде и быстрее ориентироваться в их исходном коде.
  • Предотвратите случайные глобальные переменные.Без строгого режима присвоение значения необъявленной переменной автоматически создает глобальную переменную с таким именем. Это одна из самых распространенных ошибок в JavaScript. В строгом режиме попытка сделать это приведет к ошибке.
  • Устранение скрытых угроз.При отсутствии строгого режима ссылки на это значение null или undefined автоматически приводятся к глобальному. Это может привести ко многимheadfakesа такжеpull-out-your-hairтип ошибки. В строгом режиме ссылка на это значение null или undefined вызывает ошибку.
  • Повторяющиеся значения параметров не допускаются.Строгий режим выдает ошибку, когда обнаруживает повторяющиеся именованные аргументы функции (например, function foo(val1, val2, val1) {}), выявляя ошибки, которые почти наверняка существуют в вашем коде, иначе вы могли бы потратить много времени на отслеживание. вниз .
    • Примечание: раньше (в ECMAScript 5) строгий режим запрещал повторяющиеся имена свойств (например, var object = {foo: "bar", foo: "baz"}; ), но изECMAScript 2015С самого начала это уже не так.
  • Сделайте eval() более безопасным.eval() ведет себя немного по-разному в строгом и нестрогом режимах. Кроме того, переменные и функции, объявленные внутри оператора eval(), не создаются в содержащей области видимости в строгом режиме (они создаются в содержащей области видимости в нестрогом режиме, что также может быть частым источником проблем).
  • Выдает недопустимый символ удаления с ошибкой.Оператор удаления (используемый для удаления свойства объекта) нельзя использовать для ненастраиваемых свойств объекта. Нестрогий код автоматически завершится ошибкой при попытке удалить ненастраиваемое свойство, а строгий режим в этом случае выдаст ошибку.

6. Рассмотрим следующие две функции. Будут ли они все возвращать одно и то же значение? Почему или почему нет?

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return
  {
      bar: "hello"
  };
}

Удивительно, но две функции не возвращают один и тот же результат. Вместо:

console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());

Будет производить:

foo1 returns:
Object {bar: "hello"}
foo2 returns:
undefined 

Это не только удивительно, но особенно раздражает то, что foo2() возвращает значение undefined без каких-либо ошибок.

Причина кроется в том, что точки с запятой технически необязательны в JavaScript (хотя игнорирование их, как правило, является очень плохим тоном). Таким образом, встречая строку, содержащую оператор return (и ничего больше) в foo2(),Сразу после оператора return автоматически вставляется точка с запятой.

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

Это поведение также считается соответствующим соглашению JavaScript о размещении открывающей скобки в конце строки, а не в начале новой строки. Как показано здесь, это не просто предпочтение стиля в JavaScript.

7. Что такое NaN? Каков его тип? Как надежно проверить, равно ли значение NaN?

Свойство NaN представляет значение, которое не является числом. Это специальное значение не может быть выполнено, так как один из операндов не является числовым (например, "abc"/4) или потому что результат операции не является числовым.

Хотя это может показаться простым, у NaN есть некоторые удивительные характеристики, которые могут привести к ошибкам, если люди не знают о них.

С одной стороны, хотя NaN означает «не число», его тип — число:

console.log(typeof NaN === "number");  // logs "true"

Кроме того, NaN сравнивается с чем угодно — даже с самим собой! - неверно:

console.log(NaN === NaN);  // logs "false"

Полунадежный способ проверить, равно ли число NaN, состоит в использовании встроенной функции isNaN(), но даже с использованиемisNaN() также не является хорошим решением..

Лучшее решение — использовать value! == значение, которое даст true только в том случае, если значение равно NaN. Кроме того, ES6 предоставляет новыйФункция Number.isNaN(), которая отличается от старой глобальной функции isNaN() и более надежна.

8. Что выводит следующий код? Поясните свой ответ.

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);

Грамотный ответ на этот вопрос таков: «Вы не можете быть уверены. Он может выводить 0,3 и true, а может и нет. Все числа в JavaScript обрабатываются с точностью с плавающей запятой, поэтому не всегда могут давать ожидаемые результаты».

Приведенный выше пример является классическим случаем, демонстрирующим эту проблему. Удивительно, но печатает:

0.30000000000000004
false

Типичное решение — сравнить абсолютную разницу между двумя числами с помощью специальной константы Number.EPSILON:

function areTheNumbersAlmostEqual(num1, num2) {
    return Math.abs( num1 - num2 ) < Number.EPSILON;
}
console.log(areTheNumbersAlmostEqual(0.1 + 0.2, 0.3));

Обсудите возможный способ написания функции isInteger(x), определяющей, является ли x целым числом.

Это звучит тривиально, и тот факт, что ECMAscript 6 представил новую функцию Number.isInteger() именно для этого, тривиален. Однако до ECMAScript 6 это было немного сложнее, потому что не было предоставлено эквивалента метода Number.isInteger().

Проблема в том, что в спецификации ECMAScript целые числа существуют только концептуально, т. е. числовые значения всегда хранятся как значения с плавающей запятой.

Имея это в виду, самым простым и чистым решением до ECMAScript-6 (которое достаточно надежно, чтобы возвращать false, даже если функции передается нечисловое значение (например, строка или нуль)) стал бы побитовый оператор XOR. для следующих применений:

function isInteger(x) { return (x ^ 0) === x; } 

Приведенное ниже решение также будет работать, хотя и не так элегантно, как приведенное выше.

function isInteger(x) { return Math.round(x) === x; }

Обратите внимание, что Math.ceil() или Math.floor() в равной степени могут использоваться (вместо Math.round()) в приведенной выше реализации.

или:

function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0); }

Довольно распространенное неверное решение выглядит следующим образом:

function isInteger(x) { return parseInt(x, 10) === x; }

Хотя этот подход, основанный на parseInt, хорошо работает для многих значений x, он не будет работать, когда x станет достаточно большим. Проблема в том, что parseInt() приводит свой первый аргумент к строке перед синтаксическим анализом числа. Поэтому, как только число становится достаточно большим, его строковое представление будет представлено в экспоненциальной форме (например, 1e+21). Итак, parseInt() попытается проанализировать 1e+21, но остановит анализ, когда дойдет до символа e, поэтому вернет значение 1. Обратите внимание:

> String(1000000000000000000000)
'1e+21'

> parseInt(1000000000000000000000, 10)
1

> parseInt(1000000000000000000000, 10) === 1000000000000000000000
false

9. Когда приведенный ниже код будет выполнен, в каком порядке будут выведены в консоль числа 1-4? Почему?

(function() {
    console.log(1); 
    setTimeout(function(){console.log(2)}, 1000); 
    setTimeout(function(){console.log(3)}, 0); 
    console.log(4);
})();

Значения будут регистрироваться в следующем порядке:

1
4
3
2

Давайте сначала объясним части, которые могут быть более очевидными:

  • 1 и 4 показаны первыми, потому что они были зарегистрированы без задержки простым вызовом console.log()

  • Отображается после 3, потому что 2 записывается с задержкой в ​​1000 мс (т. е. 1 секунду), а 3 записывается с задержкой в ​​0 мс.

OK. Но если 3 регистрируется с задержкой в ​​0 мс, значит ли это, что она регистрируется немедленно? И если да, то не должно ли оно быть зарегистрировано до 4, поскольку 4 регистрируется следующей строкой кода?

Ответ и правильное пониманиеСобытия JavaScript связаны со временем..

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

Аналогичным образом, если браузер занят, setTimeout() также поместит выполнение указанной функции в очередь событий.

Когда в качестве второго аргумента функции setTimeout() передается нулевое значение, она пытается выполнить указанную функцию "как можно скорее". В частности, выполнение функции помещается в очередь событий для следующего такта таймера. Обратите внимание, однако, что это не так просто: функция не будет выполняться до следующего тика. Вот почему в приведенном выше примере вызов console.log(4) происходит до вызова console.log(3) (с небольшой задержкой, поскольку вызов console.log(3) вызывается через setTimeout).

10. Напишите простую функцию (менее 160 символов), которая возвращает логическое значение, указывающее, является ли строкаpalindrome.

Следующая строка функции вернет true, если str является палиндромом; в противном случае она вернет false.

function isPalindrome(str) {
  str = str.replace(/\W/g, '').toLowerCase();
  return (str == str.split('').reverse().join(''));
}

Например:

console.log(isPalindrome("level"));                   // logs 'true'
console.log(isPalindrome("levels"));                  // logs 'false'
console.log(isPalindrome("A car, a man, a maraca"));  // logs 'true'

11. Напишите метод суммы, который будет нормально работать при вызове с использованием следующего синтаксиса.

console.log(sum(2,3));   // Outputs 5
console.log(sum(2)(3));  // Outputs 5

Есть (по крайней мере) два способа сделать это:

METHOD 1

function sum(x) {
  if (arguments.length == 2) {
    return arguments[0] + arguments[1];
  } else {
    return function(y) { return x + y; };
  }
}

В JavaScript функции предоставляют доступ к объекту параметра, который обеспечивает доступ к фактическим параметрам, переданным в функцию. Это позволяет нам использовать свойство length для определения количества аргументов, передаваемых функции во время выполнения.

Если мы передаем два аргумента, мы просто добавляем их и возвращаемся.

В противном случае мы предполагаем, что она была вызвана как sum(2)(3), поэтому мы возвращаем анонимную функцию, которая принимает аргументы, переданные в sum() (в данном случае 2), и аргументы, переданные в параметр анонимной функции в этом случае 3) .

METHOD 2

function sum(x, y) {
  if (y !== undefined) {
    return x + y;
  } else {
    return function(y) { return x + y; };
  }
}

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

12. Рассмотрим следующий фрагмент кода

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

(a) Когда пользователь нажимает «кнопку 4», что регистрируется в консоли? Почему?

(b) Обеспечить одну или несколько альтернативных реализаций, которые работают должным образом.

отвечать:

(a) Независимо от того, какую кнопку нажимает пользователь, число 5 всегда будет регистрироваться в консоли. Это связано с тем, что к моменту вызова метода onclick (для любой кнопки) цикл for завершился и переменная i уже имеет значение 5. (Бонусные баллы, если респондент достаточно знает контекст выполнения, объект переменной , объект активации и то, как внутреннее свойство «scope» влияет на поведение закрытия.)

(b) Ключом к выполнению этой работы является захват значения i каждый раз, когда он проходит через цикл for, передавая его во вновь созданный объект-функцию. Вот четыре возможных способа сделать это:

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', (function(i) {
    return function() { console.log(i); };
  })(i));
  document.body.appendChild(btn);
}

В качестве альтернативы вы можете обернуть весь вызов в новую анонимную функцию как btn.addEventListener:

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  (function (i) {
    btn.addEventListener('click', function() { console.log(i); });
  })(i);
  document.body.appendChild(btn);
}

В качестве альтернативы мы можем заменить цикл for, вызвав собственный метод forEach объекта массива:

['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function() { console.log(i); });
  document.body.appendChild(btn);
});

Наконец, самое простое решение, если вы находитесь в контексте ES6/ES2015, — это использовать let i вместо var i:

for (let i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

13. Предполагая, что d является «пустым» объектом в области видимости:

var d = {};

... что достигается с помощью приведенного ниже кода?

[ 'zebra', 'horse' ].forEach(function(k) {
    d[k] = undefined;
});

Фрагмент кода, показанный выше, задает два свойства объекта d. В идеале поиск объекта JavaScript с неустановленным ключом дает значение undefined. Но запуск этого кода помечает эти свойства как «собственные свойства» объекта.

Это полезная стратегия для обеспечения того, чтобы объект имел заданный набор свойств. Передача этого объекта в Object.keys вернет массив, содержащий эти заданные ключи (даже если их значения не определены).

14. В консоль будет выводиться следующий код, почему?

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

Зарегистрированный вывод будет:

"array 1: length=5 last=j,o,n,e,s"
"array 2: length=5 last=j,o,n,e,s"

arr1 и arr2 одинаковы (т.е. ['n', 'h', 'o', 'j', ['j', 'o', 'n', 'e', ​​'s']]) приведенный выше код выполняется по следующим причинам:

  • Вызов метода reverse() объекта массива не только возвращает массив в обратном порядке, но и меняет порядок самого массива (в данном случае это arr1).

  • Метод reverse() возвращает ссылку на сам массив (в данном случае это arr1). Таким образом, arr2 — это просто ссылка (а не копия) на arr1. Таким образом, когда мы что-либо делаем с arr2 (т. е. когда мы вызываем arr2.push(arr3);), arr1 также затрагивается, потому что arr1 и arr2 являются просто ссылками на один и тот же объект.

Вот несколько моментов, которые позволили бы ответить на этот вопрос:

  • Передача массива методу push() другого массива помещает весь массив как один элемент в конец массива. В результате объявление arr2.push(arr3); добавляет arr3 целиком в конец arr2 (т. е. не объединяет два массива, для чего предназначен метод concat()).

  • Как и Python, JavaScript распознает отрицательные индексы при вызове методов массива, таких как slice(), как способ обращения к элементам в конце массива; например, индекс -1 означает последний элемент в массиве и т. д. по аналогии.

15. В консоль будет выводиться следующий код, почему?

console.log(1 +  "2" + "2");
console.log(1 +  +"2" + "2");
console.log(1 +  -"1" + "2");
console.log(+"1" +  "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

Приведенный выше код выведет на консоль:

"122"
"32"
"02"
"112"
"NaN2"
NaN

почему это...

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

Пример 1: 1 + "2" + "2" Вывод: "122" Объяснение: Первая операция выполняется в 1 + "2". Поскольку один из операндов ("2") является строкой, JavaScript предполагает, что необходимо выполнить конкатенацию строк, и, таким образом, приводит 1 к "1" и 1 + "2" к "12". Тогда «12» + «2» дает «122».

Пример 2: 1 + + "2" + "2" Вывод: "32" Объяснение: Согласно порядку операций, первая операция, которую нужно выполнить, это + "2" (дополнительный + перед первой "2" обрабатывается как унарный оператор). Таким образом, JavaScript преобразует тип «2» в число, а затем применяет к нему унарный знак + (т. е. рассматривает его как положительное число). В результате следующая операция теперь 1 + 2, что, конечно же, дает 3. Однако у нас есть операция между числом и строкой (т. е. 3 и «2»), поэтому JavaScript снова преобразует значение в строку и выполнить конкатенацию строк, что даст "32".

Пример 3: 1 + - "1" + "2" Вывод: "02" Объяснение: Объяснение здесь такое же, как и в предыдущем примере, за исключением того, что унарный оператор - - вместо +. Таким образом, "1" становится 1, затем становится -1, когда - применяется, затем добавляет 1, чтобы получить 0, затем преобразуется в строку и объединяется с последним операндом "2", дает "02" .

Пример 4: + "1" + "1" + "2" Вывод: "112" Объяснение: Хотя первый операнд "1" преобразуется на основе числового типа предшествующего ему унарного оператора +, когда он совпадает с the first Два операнда «1» при объединении вместе возвращают строку, которая затем объединяется с последним операндом «2» для получения строки «112».

Пример 5: "A" - "B" + "2" Вывод: "NaN2" Объяснение: Поскольку оператор "-" не может применяться к строкам и ни "A", ни "B" не могут быть преобразованы в числовые значения, "-" B" производит NaN, который затем объединяется со строкой "2" для получения "NaN2".

Пример 6: "A" - "B" + 2 Вывод: NaN Объяснение: В предыдущем примере "A" - "B" дает NaN. Но любой оператор, применяемый к NaN и другим числовым операндам, по-прежнему производит NaN.

16. Следующий рекурсивный код вызовет переполнение стека, если список массивов слишком велик. Как вы решаете эту проблему и при этом сохраняете рекурсивный режим?

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};

Потенциального переполнения стека можно избежать, изменив функцию nextListItem следующим образом:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

Переполнение стека исключено, поскольку цикл событий обрабатывает рекурсию, а не стек вызовов. Когда выполняется nextListItem, если item не равно null, функция тайм-аута (nextListItem) помещается в очередь событий, и функция завершается, обнуляя стек вызовов. Когда очередь событий запускает событие тайм-аута, обрабатывается следующий элемент, и таймер устанавливается для повторного вызова nextListItem. Поэтому метод обрабатывается от начала до конца без прямого рекурсивного вызова, поэтому стек вызовов остается чистым независимо от количества итераций.

17. Что такое «замыкание» в JavaScript? Например.

Замыкание — это внутренняя функция, которая имеет доступ к переменным в цепочке областей действия внешней (включающей) функции. Замыкание может обращаться к переменным в трех областях видимости, а именно: (1) к переменным в их собственной области видимости, (2) к переменным в области действия объемлющей функции и (3) к глобальным переменным.

Вот пример:

var globalVar = "xyz";

(function outerFunc(outerArg) {
    var outerVar = 'a';

    (function innerFunc(innerArg) {
    var innerVar = 'b';

    console.log(
        "outerArg = " + outerArg + "\n" +
        "innerArg = " + innerArg + "\n" +
        "outerVar = " + outerVar + "\n" +
        "innerVar = " + innerVar + "\n" +
        "globalVar = " + globalVar);

    })(456);
})(123);

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

outerArg = 123
innerArg = 456
outerVar = a
innerVar = b
globalVar = xyz

18. Что выводит следующий код:

for (var i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

Поясните свой ответ. Как использовать замыкания здесь?

Показанный пример кода не показывает значения 0,1,2,3 и 4, как можно было бы ожидать, вместо этого он показывает 5,5,5,5.

Это связано с тем, что каждая функция, выполняемая внутри цикла, будет выполняться после завершения всего цикла, поэтому все функции будут ссылаться на последнее значение, хранящееся в i, равное 5.

Создавая для каждой итерацииуникальная сфера, вы можете использовать замыкание, чтобы предотвратить эту проблему, и хранить каждое уникальное значение этой переменной в своей области видимости, например так:

for (var i = 0; i < 5; i++) {
    (function(x) {
        setTimeout(function() { console.log(x); }, x * 1000 );
    })(i);
}

Это дает возможные результаты записи 0, 1, 2, 3 и 4 в консоль.

существуетВ контексте ЕС2015, вы можете просто использовать let вместо var в исходном коде:

for (let i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

19. В консоль выводятся следующие строки кода?

console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));

Поясните свой ответ.

Код выведет следующие четыре строки:

0 || 1 = 1
1 || 2 = 1
0 && 1 = 0
1 && 2 = 2

В JavaScript и ||, и && являются логическими операторами, которые возвращают первое полностью детерминированное «логическое значение» при вычислении слева направо.

или (||) оператор. В выражении формы X||Y сначала вычисляется X и интерпретируется как логическое значение. Если это логическое значение равно true, возвращается true (1), а Y не вычисляется, поскольку условие «или» уже выполнено. Однако, если это логическое значение «ложь», мы все еще не знаем, является ли X||Y истинным или ложным, пока мы не оценим Y и не интерпретируем его как логическое значение.

Следовательно, 0||1 оценивается как true (1), точно так же, как 1||2.

и (&&) оператор. В выражении формы X && Y, X сначала оценивается и интерпретируется как логическое значение. Если это логическое значение равно false, возвращается false(0) и Y не оценивается, поскольку условие "и" не выполнено. Однако, если это логическое значение «истинно», мы все еще не знаем, является ли X && Y истинным или ложным, пока мы не оценим Y и не интерпретируем его как логическое значение.

Однако что интересно в операторе &&, так это то, что когда выражение оценивается как «истина», он возвращает само выражение. Это нормально, потому что в логических выражениях рассматривается как «истина», но также может использоваться для возврата значения, когда вам это нужно. Это объясняет, почему, как ни странно, 1 && 2 возвращает 2 (хотя вы могли бы ожидать, что она вернет true или 1).

20. Что будет на выходе при выполнении следующего кода? проиллюстрировать.

console.log(false == '0')
console.log(false === '0')

Этот код выведет:

true
false

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

21. Что выводит следующий код? Поясните свой ответ.

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);

Вывод этого кода будет 456 (не 123).

И вот почему: JavaScript неявно объединяет значения параметров при установке свойств объекта. В этом случае, поскольку и b, и c являются объектами, они оба будут преобразованы в «[object Object]». Следовательно, как a[b], так и a[c] эквивалентны ["[object Object]"] и могут использоваться взаимозаменяемо. Следовательно, установка или ссылка на [c] точно такие же, как установка или ссылка на [b].

22. В консоль будет выведен следующий код.

console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

Этот код выведет значение факториала 10 (т.е. 10! или 3 628 800).

Причины следующие:

Именованная функция f() вызывает себя рекурсивно, пока не вызовет f(1), которая просто возвращает 1. Итак, вот что она делает:

f(1): returns n, which is 1
f(2): returns 2 * f(1), which is 2
f(3): returns 3 * f(2), which is 6
f(4): returns 4 * f(3), which is 24
f(5): returns 5 * f(4), which is 120
f(6): returns 6 * f(5), which is 720
f(7): returns 7 * f(6), which is 5040
f(8): returns 8 * f(7), which is 40320
f(9): returns 9 * f(8), which is 362880
f(10): returns 10 * f(9), which is 3628800

23. Рассмотрим следующий фрагмент кода. Что такое вывод консоли и почему?

(function(x) {
    return (function(y) {
        console.log(x);
    })(2)
})(1);

На выходе будет 1, хотя значение x никогда не устанавливается во внутренней функции. Причины следующие:

как нашРуководство по найму JavaScriptКак объяснялось в разделе , замыкание — это функция и все переменные или функции, которые находились в области видимости при создании замыкания. В JavaScript замыкания реализованы как «внутренние функции», то есть функции, определенные в теле другой функции. Важной особенностью замыканий является то, что внутренняя функция по-прежнему может обращаться к переменным внешней функции.

Так что в этом примере, поскольку x не определен во внутренней функции, в области видимости внешней функции ищется определенная переменная x, имеющая значение 1.

24. Следующий код будет выведен на консоль и почему

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};

var stoleSecretIdentity = hero.getSecretIdentity;

console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

Что не так с этим кодом и как это исправить.

Этот код выведет:

undefined
John Doe

Первый console.log печатает undefined, потому что мы извлекаем метод из объекта-героя, поэтому stoleSecretIdentity() вызывается в глобальном контексте (то есть объекте окна), где атрибут _name не существует.

Один из способов исправления функции stoleSecretIdentity() заключается в следующем:

var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

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

Аргументы этой функции должны быть:

  • элемент DOM
  • функция обратного вызова (принимает элемент DOM в качестве параметра)

Посещение всех элементов дерева (DOM) — это [классический алгоритм поиска в глубину].Depth-First-Search algorithmзаявление. Вот пример решения:

function Traverse(p_element,p_callback) {
   p_callback(p_element);
   var list = p_element.children;
   for (var i = 0; i < list.length; i++) {
       Traverse(list[i],p_callback);  // recursive call
   }
}

27. Проверьте свои знания в JavaScript: что выводит следующий код?

var length = 10;
function fn() {
    console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

выход:

10
2

Почему не 10 и 5?

Во-первых, поскольку fn передается в метод функции в качестве аргумента, областью действия (this) функции fn является окно. var length = 10; объявлено на уровне окна. К нему также можно обращаться как к window.length или length или this.length (когда this === window).

Метод привязан к Object obj, и obj.method вызывается с параметрами fn и 1. Хотя метод принимает только один параметр, он вызывается с уже переданными двумя параметрами: первый — обратный вызов функции, второй — просто число.

Когда fn() вызывается во внутреннем методе, функция передается как параметр на глобальном уровне, this.length будет иметь доступ к var length = 10 (объявленному глобально), определенному в Object obj, вместо length = 5.

Теперь мы знаем, что можем использовать массив arguments[] для доступа к любому количеству аргументов в функции JavaScript.

Таким образом, arguments0 — это не что иное, как вызов fn(). Внутри fn область действия этой функции становится массивом параметров, и запись длины параметра [] возвращает 2.

Таким образом, вывод будет таким, как указано выше.

28. Рассмотрим следующий код. Что на выходе и почему?

(function () {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();

1
undefined
2

Операторы var приостанавливаются (без инициализации их значений) до верхней части глобальной или функциональной области, к которой они принадлежат, даже если они находятся внутри блока with или catch. Однако неверный идентификатор виден только внутри блока catch. Это эквивалентно:

(function () {
    var x, y; // outer and hoisted
    try {
        throw new Error();
    } catch (x /* inner */) {
        x = 1; // inner x, not the outer one
        y = 2; // there is only one y, which is in the outer scope
        console.log(x /* inner */);
    }
    console.log(x);
    console.log(y);
})();

29. Что выводит этот код?

var x = 21;
var girl = function () {
    console.log(x);
    var x = 20;
};
girl ();

21, а не 20, результат "не определен"

Это связано с тем, что инициализация JavaScript не приостанавливается.

(Почему она не показывает глобальное значение 21? Причина в том, что когда функция выполняется, она проверяет, существует ли локальная переменная x, но она не была объявлена, поэтому она не ищет глобальную переменную .)

30. Как вы клонируете объект?

var obj = {a: 1 ,b: 2}
var objclone = Object.assign({},obj);

Теперь значение objclone равно {a:1,b:2}, но оно указывает на другой объект, чем obj.

Но остерегайтесь потенциальных ловушек: Object.clone() будет выполнять только поверхностную копию, а не глубокую. Это означает, что вложенные объекты не копируются. Они по-прежнему ссылаются на те же вложенные объекты, что и оригинал:

let obj = {
    a: 1,
    b: 2,
    c: {
        age: 30
    }
};

var objclone = Object.assign({},obj);
console.log('objclone: ', objclone);

obj.c.age = 45;
console.log('After Change - obj: ', obj);           // 45 - This also changes
console.log('After Change - objclone: ', objclone); // 45

for (let i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

31. Что напечатает этот код?

for (let i = 0; i < 5; i++) {
    setTimeout(function() { console.log(i); }, i * 1000 );
}

Он напечатает 0 1 2 3 4, потому что здесь мы используем let вместо var. Переменную i можно увидеть только в области блока цикла for.

32. Что выводят следующие строки и почему?

console.log(1 < 2 < 3);
console.log(3 > 2 > 1);

Первый оператор возвращает true, как и ожидалось.

Второй возвращает false из-за того, как механизм работает для ассоциативности операторов . Он сравнивает слева направо, поэтому 3>2>1 JavaScript преобразуется в true>1. true имеет значение 1, поэтому он сравнивает 1>1, что неверно.

33. Как добавить элементы в начало массива? Как добавить один в конце?

var myArray = ['a', 'b', 'c', 'd'];
myArray.push('end');
myArray.unshift('start');
console.log(myArray); // ["start", "a", "b", "c", "d", "end"]

В ES6 вы можете использовать оператор распространения:

myArray = ['start', ...myArray];
myArray = [...myArray, 'end'];

Или, короче:

myArray = ['start', ...myArray, 'end'];

34. Представьте, что у вас есть такой код:

var a = [1, 2, 3];

а) Это вызовет сбой?

a[10] = 99;

б) Что это за выход?

console.log(a[6]);

а) Не падает. Движок JavaScript сделает слоты массива с 3 по 9 "пустыми слотами".

б) Здесь a[6] выведет неопределенное значение, но слот по-прежнему пуст, а не неопределен. В некоторых случаях это может быть важным нюансом. Например, при использовании map() пустые слоты в выводе map() останутся пустыми, но неопределенные слоты будут переназначены с помощью переданной ей функции:

var b = [undefined];
b[2] = 1;
console.log(b);             // (3) [undefined, empty × 1, 1]
console.log(b.map(e => 7)); // (3) [7,         empty × 1, 7]

35. Каково значение typeof undefined == typeof NULL?

Выражение будет оцениваться как истинное, поскольку NULL будет рассматриваться как любая другая неопределенная переменная.

Примечание. JavaScript чувствителен к регистру, здесь мы используем NULL вместо null.

36. Что происходит после возврата кода?

console.log(typeof typeof 1);

string

typeof 1 вернет «число», typeof «число» вернет строку.

37. Что выводит следующий код? Почему?

var b = 1;
function outer(){
       var b = 2
    function inner(){
        b++;
        var b = 3;
        console.log(b)
    }
    inner();
}
outer();

Вывод на консоль будет "3".

В этом примере есть три замыкания, каждое со своим собственным объявлением var b. Когда вызывается переменная, замыкание проверяется от локального к глобальному, пока не будет найден экземпляр. Поскольку внутреннее замыкание имеет собственную переменную b, это выход.

Кроме того, поскольку boost код внутри будет интерпретироваться следующим образом:

function inner () {
    var b; // b is undefined
    b++; // b is NaN
    b = 3; // b is 3
    console.log(b); // output "3"
}

Собеседований больше, чем сложных технических вопросов, так что это всего лишь рекомендации. Не каждый кандидат категории «отлично», достойный найма, сможет ответить на все вопросы, и не всем им будет гарантирован кандидат категории «отлично».В конце концов, рекрутинг по-прежнему остается искусством, наукой и большим объемом работы..