Хабрахабр

Разбор вопросов на стенде hh.ru на #HolyJS18

Очень надеюсь что у нас получилось. Мы старались сделать для вас что-то интересное и необычное. Давайте разбираться. Нам не хотелось оставлять вас без ответов и объяснений почему именно так.

Для начала хочу напомнить как проходил конкурс, было 4 тура по 15 вопросов про JS, 1 внеконкурсный тур на 15 вопросов про React и финал на 10 вопросов.

image

Под катом — разбор задач первых 4 туров.

Это вторая часть наших разборов.
Разбор вопросов про React тут

Мы решили что нам нужно нагенерировать порядка 80-90 вопросов, чтобы был запас из чего выбирать. Как мы все это делали? После этого мы поделили все на темы:

  • события в браузере
  • различные API (Array, Set, defineProperty и т.д.),
  • внимательность
  • работа с дробными числами
  • поднятие (hoisting)
  • event loop
  • приведение типов
  • typeof
  • логичиские (с логическим И и ИЛИ)

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

Давайте начинать с самой простой. Из-за этого разбирать по турам кажется не очень удобно потому-что будет очень много дублирующихся объяснений, предлагаю смотреть на них по темам.

Что будет выведено в консоль?

console.log(0,1 + 0,2); a) 0.30000000000000004 b) 0.3 c) 2 d) 0 1 2

Ответ + разбор

d) 0 1 2
Тут между числами стоит ,, а не . если отформатировать вопрос так:
console.log(0, 1 + 0, 2); все станет понятно

Что будет выведено в консоль?

(() => { 'use strict'; a = null + undefined; console.log(a);
})(); a) 0 b) NaN c) null d) ошибка

Ответ + разбор

будет создана глобальная переменная window.a в строгом режиме такое запрещено. d) ошибка
a создается не как переменная (не Variable Declaration), тут происходит неявное присваивание (Assignment Expression) к this.a что очень часто может оказаться не тем чего вы ожидаете, т.к.

Что будет выведено в консоль?

let foo = function bar() ; console.log( typeof bar() ); a) 'function' b) 'number' c) 'undefined' d) ошибка

Ответ + разбор

Для вызова функции надо вызывать foo, а не bar. d) ошибка
Это функциональное выражение (expression) — имя функции в данном случае является локальным для функции. Если бы это было объявление (declaration) ответ был бы number.

Что будет выведено в консоль?

console.log(0.1 ** 2); a) 0.2 b) 0.01 c) 0.010000000000000002 d) NaN

Ответ

010000000000000002 c) 0.

Что будет выведено в консоль?

console.log(0.1 + 0.2); a) 0.30000000000000004 b) 0.3 c) 2 d) NaN

Ответ + разбор

30000000000000004
** — это аналог Math.pow возводим 0. a) 0. 01, но в JS (как и во многих других языках) есть известная проблема с точностью операций при работе с числами с плавающей запятой. 1 в квадрат — должно получиться 0. 010000000000000002 Связано это с тем что в двоичной системе получается бесконечная дробь т.к. Будет 0. Тоже самое произойдет при сложении. под число в JS всегда выделяется ровно 64 бита — все числа всегда двойной точности с плавающей запятой.

Переходим к вопросам чуть сложнее.

Есть обработчик события на элементе, Какие значения внутри этого обработчика будут всегда одинаковы?

elem.onclick = function(event) { } a) event.target и event.currentTarget b) event.target и this c) event.currentTarget и this d) все варианты не верны

Ответ + разбор

c) event.currentTarget и this
this — всегда будет указывать на элемент
currentTarget — тот элемент на котором висит событие
target — элемент на котором событие произошло

Что выведет этот код по клику на div?

div.onclick = function() { console.log(1) };
div.onclick = function() { console.log(2) };
div.addEventListener('click', function() { console.log(3) }); a) 1 b) 1 3 c) 2 3 d) 3

Ответ + разбор

onclick — это DOM свойство оно всегда одно
События сработают в том порядке в котором навешены, сначала будет выведено 2 потом 3.
Если бы мы несколько раз делали addEventListener то сработал бы каждый из них, т.к. c) 2 3
onclick добавит обработчик console.log(1), но в следующей строчке мы перетираем его новой функцией и остается только console.log(2). хендлеры добавляют события в очередь.

Секция вопросов про различные API

Что выведет этот код?

(() => { const obj = { key: 1 }; Object.defineProperty(obj, 'key', { enumerable: false, configurable: false, writable: false, value: 2 }); console.log(obj.key); obj.key = 3; console.log(obj.key);
})(); a) 1, 2 b) 2, 2 c) 2, 3 d) ошибка

Ответ

Что выведет этот код?

(() => { 'use strict'; const obj = { key: 1 }; Object.defineProperty(obj, 'key', { enumerable: false, configurable: false, writable: false, value: 2 }); console.log(obj.key); obj.key = 3; console.log(obj.key);
})(); a) 1, 2 b) 2, 2 c) 2, 3 d) 2, ошибка

Ответ

d) 2, ошибка

Что выведет этот код?

(() => { const obj = { key: 1 }; Object.defineProperty(obj, 'key', { enumerable: false, configurable: false, writable: true, value: 2 }); console.log(obj.key); obj.key = 3; console.log(obj.key);
})(); a) 1, 2 b) 2, 2 c) 2, 3 d) ошибка

Ответ + разбор

Если она установлена в false то запрещается менять значения ключу переданному вторым параметром в defineProperty. c) 2, 3
Во всех вопросах выше проверяется знание метода defineProperty а конкретнее настройки writable. Разница только в том что без строгого режима — use strict движок притворится что все хорошо, но значение не поменяет, а в в строгом режиме будет ошибка.

Что выведет этот код?

let x = 5;
console.log(x++); a) 5 b) 6 c) '5++' d) ошибка

Ответ

Что выведет этот код?

const a = 5;
console.log(a++); a) 5 b) 6 c) '5++' d) ошибка

Ответ

console.log(++5) распечатало бы 6
const нельзя перезаписывать, а т.к. d) ошибка
При использовании постфиксной формы инкримента возвращается значение до увеличения.
А при префиксной после, т.е. Number — это примитив то при его увеличении переменную перезапишет новым значением и будет ошибка.

Что выведет этот код?

const a = [...new Set([1, 1, 2, , 3, , 4, 5, 5])];
console.log(a); a) [1, 1, 2, , 3, , 4, 5, 5] b) [1, 2, undefined, 3, 4, 5] c) [1, 1, 2, undefined, 3, undefined, 4, 5, 5] d) ошибка

Ответ

b) [1, 2, undefined, 3, 4, 5]

Что выведет этот код?

let set = new Set([10, '10', new Number(10), 1e1, 0xA]);
console.log(set.size); a) 5 b) 3 c) 2 d) 1

Ответ

Что выведет этот код?

let obj = {};
let set = new Set([obj, obj, {}, {}, {...{}}, {...obj}]);
console.log(set.size); a) 6 b) 5 c) 2 d) 1

Ответ

Вопрос в том как эти значения сравниваются. b) 5
Set — это множество, в нем по определению не может быть одинаковых значений. объекты будут созданы по новой в разных местах памяти и их ссылки будут не равны. Примитивы сравниваются по значению, а объекты по ссылке.
Он сам по себе не приводит типы данных и может хранить значения любого типа 1e1 и 0xA — будут преобразованы в десятичную систему и получится 10.
А новые объекты всегда не равны: console.log({} == {}) выдаст false т.к.

Что выведет этот код?

console.log(Infinity / Infinity); a) NaN b) 1 c) Error d) Infinity

Ответ

с математической точки зрения получается неопределенность, то же самое произойдет при умножении Infinity и 0 ошибок математические операции не вызывают — будет NaN a) NaN
Делить бесконечность на бесконечность и вычесть бесконечность из бесконечности нельзя т.к.

Что выведет этот код?

const a = { ...{ a: 1, b: 2, c: 3 }, ...{ a: 2, c: 4, d: 8 } };
console.log(a); a) { a: 2, b: 2, c: 4, d: 8 } c) { a: 1, b: 2, c: 3, d: 8 } c) { a: 1, b: 2, c: 3, a: 2, c: 4, d: 8 }
d) ошибка

Ответ

a) { a: 2, b: 2, c: 4, d: 8 }

Что выведет этот код?

const a = [...[1, 2], ...[[3, 4]], ...[5, 6]];
console.log(a); a) [1, 2, 3, 4, 5, 6] b) [1, 2, [3, 4], 5, 6] c) [[1, 2], [[3, 4]], 5, 6] e) ошибка

Ответ + разбор

Берет значения из сущности после ... и копирует их в создаваемую. b) [1, 2, [3, 4], 5, 6]
Spread оператор служит для разбора объекта или массива на части. ...[[1]] вернет массив с одним элементом, а не сам элемент. Стоит отметить что для массива и объекта раскрывается на 1 уровень т.е. Это можно использовать для указания параметров по умолчанию. В объектах же дублирующихся значений быть не может, поэтому значения, раскрываемые после, перезапишут те, что были раскрыты раньше.

const fn = (actualProps) => ({ ...defaultProps, ...actualProps })

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

Что выведет этот код?

console.log(parseInt(' -10,3 миллиона рублей ')); a) -10,3 b) -10 c) TypeError d) NaN

Ответ + разбор

parseInt отсекает дробную часть числа. b) -10
Исчерпывающее описание с MDN:
Если функция parseInt встречает символ, не являющийся числом в указанной системе счисления, она пропускает этот и все последующие символы (даже, если они подходящие) и возвращает целое число, преобразованное из части строки, предшествовавшей этому символу. Пробелы в начале и конце строки разрешены.

Что выведет этот код?

const t = { a: 6, b: 7 };
const p = new Proxy(t, { get() { return 12; },
}); console.log(p.a);
p.a = 18;
console.log(p.a);
console.log(t.a); a) ошибка b) 12 18 18 c) 12 18 6 d) 12 12 18 e) 6 18 6

Ответ + разбор

В данном случае мы проксируем только get метод и всегда возвращаем 12 независимо от того, к какому полю объекта мы обращаемся. d) 12 12 18
Proxy перехватывает все обращения к объекту. При этом set мы не трогаем, и при обращении к прокси значение в объекте будет заменено.

Что выведет этот код?

let arr = [];
arr[1] = 1;
arr[5] = 10;
console.log(arr.length); a) 1 b) 5 c) 6 d) 10

Ответ

Что выведет этот код?

let arr = new Array(3);
console.log(arr[1]); a) undefined b) 1 c) 3 d) ошибка

Ответ + разбор

Массив при этом создается пустой, все значения undefined. a) undefined
Когда мы создаем Array с одним числовым аргументом — он означает длину массива. Стоит отметить, что если передать в Array не число, вернется массив с этим элементом т.е. То же самое произойдет если создать обратиться к несуществующему полю массива. Array('a') вернет ['a']

Что выведет этот код?

console.log([] && 'foo' && undefined && true && false); a) [] b) 'foo' c) undefined d) true

Ответ

c) undefined

Что выведет этот код?

console.log(0 || 1 && 2 || 3); a) 0 b) 1 c) 2 d) 3

Ответ

Что выведет этот код?

console.log(0 || '' || 2 || undefined || true || false); a) 0 b) false c) 2 d) true

Ответ

Что выведет этот код?

console.log(2 && '1' && null && undefined && true && false); a) 2 b) false c) undefined d) null

Ответ

Что выведет этот код?

console.log([] && {} || null && 100 || ''); a) true b) 100 c) '' d) {}

Ответ + разбор

d) {}
Пустой масcив [] — это true как и пустой объект {}.
Пустая строка '', null и undefined — это false
Логическое или || — возвращает левый операнд, если он истинен, в остальных случаях возвращает правый.
Логическое и && — возвращает левый операнд, если он ложен, в остальных случаях возвращает правый.

Это можно иногда встретить в коде, до появления параметров по умолчанию часто писали так — если в функции нет параметров, то берем параметры по умолчанию:

function f(userParams) { var params = userParams || defaultParams;
}

Сейчас в React’е часто проверяют, если условие истинно, то рендерим что-то:

{ isDivVisible && <div>bla-bla</div> }

Что выведет этот код?

const arrayFoo = [1, 2, 3, 4];
const arrayBaz = [1, 2, 3, 4]; console.log(arrayFoo == arrayBaz && arrayFoo == arrayBaz); a) false b) true c) undefined d) ошибка

Ответ

Что выведет этот код?

console.log([null, 0, -0].map(x => 0 <= x)); a) [false, true, false] b) [false, true, true] c) [false, false, false] d) [true, true, true]

Ответ

d) [true, true, true]

Что выведет этот код?

const arrayFoo = [1, 2, 3, 4];
const arrayBaz = [1, 2, 3, 4]; console.log(arrayFoo >= arrayBaz && arrayFoo <= arrayBaz); a) true b) false c) undefined d) ошибка

Ответ

Что выведет этот код?

const foo = [1, 2, 3, 4];
const baz = '1,2,3,4'; console.log(foo >= baz && foo <= baz); a) false b) true c) undefined d) будет ошибка

Ответ + разбор

Далее происходит преобразование в примитивное значение вызовом метода toString, который в свою очередь вернет строковое представление массива в виде "1,2,3,4" сравнит лексикографически два массива и вернет true b) true
При == сравниваем по ссылке.
при операции >, >=, <, <= операнды преобразуются к примитивам и у arrayFoo вызывается метод valueOf, который должен вернуть примитивное значение arrayFoo, но он возвращает ссылку на этот же массив.

Что выведет этот код?

console.log(+0 == -0);
console.log(+0 === -0);
console.log(Object.is(+0, -0)); a) true, false, false b) true, true, false c) false, true, true d) false, false. false

Ответ + разбор

Оператор === (также как и оператор ==) считает числовые значения -0 и +0 равными, а значение Number. b) true, true, false
Исчерпывающее объяснение с MDN:
Поведение этого метода (речь об Object.is) не аналогично оператору ===. NaN
не равным самому себе.

Вопросы про hoisting:

Что выведет этот код?

console.log(str);
const str = 'HeadHunter'; a) 'HeadHunter' b) undefined c) ошибка

Ответ

c) ошибка

Что выведет этот код?

var arrayFunction = []; for (let i = 0; i <= 10; i++) { arrayFunction.push(() => i); } console.log(arrayFunction[3]()); a) 4 b) 0 c) 11 d) 3

Ответ

Что выведет этот код?

console.log(str);
var str = 'HeadHunter'; a) 'HeadHunter' b) undefined c) null c) будет ошибка

Ответ

b) undefined

Что выведет этот код?

console.log(foo);
var foo;
foo = foo ? 1 : 0;
console.log(foo); a) ошибка b) undefined 0 c) '' 1 d) 0 0

Ответ

b) undefined 0

Сработает ли вызов функции?

getCompanyName(); function getCompanyName() { return 'HeadHunter';
} a) да b) нет, вызов должен стоять после объявления. c) ошибка

Ответ

Что выведет этот код?

var arrayFunction = []; for (var i = 0; i <= 10; i++) { arrayFunction.push(() => i);
} console.log(arrayFunction[3]()); a) 4 b) 0 c) 11 d) 3

Ответ + разбор

c) 11

ограничены {}. Объявления функции (declaration) всплывают, а выражения (expression) нет.
var всплывает, но до момента инициализация равен undefined.
let и const не всплывают и имеют область видимость в блок т.е.

Чтобы правильно работал цикл с var надо использовать замыкание, в нем сохранится значение.
(раньше это было классической задачей для собеседований, а сейчас у нас есть let)

var arrayFunction = []; for (var i = 0; i <= 10; i++) { (function(i) { arrayFunction.push(() => i); })(i);
} console.log(arrayFunction[3]());

Что выведет этот код?

console.log(true + false); a) true b) false c) 1 d) 0

Ответ + разбор

Получается 1 + 0 c) 1
Ни один из операторов строкой не является, + приводит к числу.

Что выведет этот код?

console.log([] - 100 + ![]); a) false b) '-100' c) -100 d) NaN

Ответ + разбор

c) -100
Массив приводится к строке, после этого из-за - приводим к числу, получается -100, далее приводим массив к false, а это 0

Что выведет этот код?

console.log([[], []] + 1); a) 1 b) '1' c) ',1' d) NaN

Ответ + разбор

[].toString вернет пустую строку ''. c) ',1'
Вызываем toString на объекте, при этом toString будет так же вызван на всех элементах массива. Получается , + 1 — ответ ,1.

Что выведет этот код?

console.log([] + 100 + 5); a) 105 b) '1005' c) 1005 d) NaN

Ответ + разбор

b) '1005'
Массив приводим к строке, и далее уже происходит конкатенация.

Что выведет этот код?

console.log(1 + { a: 3 } + '2'); a) 6 b) '1[object Object]2' c) 3 d) NaN

Ответ + разбор

b) '1[object Object]2'
Преобразовываем к строке — тут просто конкатенация.

Что выведет этот код?

console.log(10.toString() + 10 + 0x1); a) '10101' b) 21 c) '10100x1' d) ошибка

Ответ + разбор

d) ошибка
Для числа точка . означает начало дробной части, мы ожидаем там число — будет ошибка.
Чтобы этот пример заработал нормально надо писать 10..toString()

Что выведет этот код?

console.log(5 + false - null + true); a) '0true' b) NaN c) 6 d) будет ошибка

Ответ + разбор

c) 6
Тут все приводим к числу, получается 5 + 0 - 0 + 1

Что выведет этот код?

console.log(true + NaN + false); a) true b) NaN c) false d) 1

Ответ + разбор

b) NaN
Приводим все к числу, при сложении чисел с NaN — получаем NaN

Что выведет этот код?

console.log('0x1' + '1' - '1e1'); a) 17 b) 7 c) '0x111e1' d) NaN

Ответ + разбор

Из-за знака - приводим все к числу.
0x11 — шестнадцатеричная запись числа в десятичной это 17.
1e1 — экспоненциальная форма тоже самое что 1 * 10 ** 1 — т.е. b) 7
Тут уже строки после первой конкатенации получаем: '0x11' - '1e1'. просто 10.

Что выведет этот код?

let foo = () => { return null; }; console.log( typeof typeof foo ); a) 'function' b) 'string' c) 'null' d) ошибка

Ответ

b) 'string'

Что выведет этот код?

typeof function() {}.prototype; a) 'function' b) 'object' c) 'undefined'
d) ошибка

Ответ + разбор

Объекты Function наследуются от Function.prototype. b) 'object'
typeof всегда возвращает строку, имеет меньший приоритет чем вызов функции, поэтому сначала выполняется функция, а typeof применяется к возвращаемому ей результату. Спека явно определяет что это объект.

Начнем с 2 вопросов про промисы.

Что выведет этот код?

Promise.reject() .then(() => console.log(1), () => console.log(2)) .then(() => console.log(3), () => console.log(4)); a) 1 4 b) 1 3 c) 2 3 d) 2 4

Ответ

Что выведет этот код?

Promise.reject('foo') .then(() => Promise.resolve('bar'), () => {}) .then((a) => {console.log(a)}) a) foo b) bar c) undefined d) ошибка

Ответ + разбор

Если происходит ошибка до этого then, то мы попадаем в onReject колбек. c) undefined
Promise.reject — возвращает промис в rejected состоянии.
Надо вспомнить что then принимает 2 параметра, onFulfill и onReject колбеки. И еще не забываем что () => {} возвращает не пустой объект, а undefined, чтобы вернуть пустой объект надо писать так: () => ({}) Если в нем не происходит ошибки то дальше мы попадает в onFulfill следующего then.

Что выведет этот код?

async function get1() { return 1;
} function get2() { return 2;
} (async () => { console.log(await get1());
})(); console.log(get2()); a) 1,2 b) 2,1 c) 1 d) 2

Ответ

Что выведет этот код?

setTimeout(() => {console.log('in timeout')}); Promise.resolve() .then(() => {console.log('in promise')}); console.log('after'); a) in timeout, in promise, after b) after, in promise, in timeout c) after, in timeout, in promise d) in timeout, after, in promise

Ответ

b) after, in promise, in timeout

Что выведет этот код?

let __promise = new Promise((res, rej) => { setTimeout(res, 1000);
}); async function test(i) { await __promise; console.log(i);
} test(1);
test(2); a) 1, 2 b) 2, 1 c) 1 d) 2

Ответ

Что выведет этот код?

console.log('FUS');
setTimeout(() => {console.log('RO')})
Promise.resolve('DAH!').then(x => console.log(x)); a FUS RO DAH! b) FUS DAH! RO c) RO FUS DAH! d) DAH! RO FUS

Ответ

RO b) FUS DAH!

Что выведет этот код?

console.log(1);
setTimeout(() => console.log('setTimeout'), 0); console.log(2);
Promise.resolve().then(() => console.log('promise1 resolved')); console.log(3); a) 1, 2, 3, 'setTimeout', 'promise1 resolved' b) 1, 'setTimeout', 2, 'promise1 resolved', 3 c) 1, 2, 3, 'promise1 resolved', 'setTimeout' d) 1, 2, 'promise1 resolved', 3, 'setTimeout'

Ответ + разбор

Сначала выполняются микротаски — промисы и mutation observer. c) 1, 2, 3, 'promise1 resolved', 'setTimeout'
Сначала срабатывают все синхронные вызовы, после этого, когда call stack пуст, вызывается то что попало в очередь (асинхронные задачи). После этого выполняются макро таски — таймауты.
Это очень упрощенный пример, более подробно я бы советовал посмотреть выступление Михаила Башурова В конце текущей таски выполняются все микротаски, в связи с этим микротасками можно заблокировать event loop, после того как таска завершена в браузере происходит рендеринг.

Что выведет этот код?

const p = Promise.resolve(); (async () => { await p; console.log('1');
})(); p.then(() => console.log('2')) .then(() => console.log('3'));

a) 1 2 3
b) 2 1 3
c) 2 3 1
d) 3 2 1

Ответ + разбор

c) 2 3 1

Спека. Согласно спеке сначала должны выполнится промисы добавленные через then и только после этого нужно продолжить
выполнение асинхронной функции. Для более подробного понимания, почему это так, советую почитать отличную статью на v8.dev

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»