- PVSM.RU - https://www.pvsm.ru -
Мы старались сделать для вас что-то интересное и необычное. Очень надеюсь что у нас получилось. Нам не хотелось оставлять вас без ответов и объяснений почему именно так. Давайте разбираться.
Для начала хочу напомнить как проходил конкурс, было 4 тура по 15 вопросов про JS, 1 внеконкурсный тур на 15 вопросов про React и финал на 10 вопросов.
Под катом — разбор задач первых 4 туров.
Это вторая часть наших разборов.
Разбор вопросов про React тут [1]
Как мы все это делали? Мы решили что нам нужно нагенерировать порядка 80-90 вопросов, чтобы был запас из чего выбирать. После этого мы поделили все на темы:
После этого распределили вопросы по 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) ошибка
d) ошибка
a
создается не как переменная (не Variable Declaration), тут происходит неявное присваивание (Assignment Expression) к this.a
что очень часто может оказаться не тем чего вы ожидаете, т.к. будет создана глобальная переменная window.a
в строгом режиме такое запрещено.
let foo = function bar() { return 123; };
console.log( typeof bar() );
a) 'function'
b) 'number'
c) 'undefined'
d) ошибка
d) ошибка
Это функциональное выражение (expression) — имя функции в данном случае является локальным для функции. Для вызова функции надо вызывать foo
, а не bar
. Если бы это было объявление (declaration) ответ был бы number
.
console.log(0.1 ** 2);
a) 0.2
b) 0.01
c) 0.010000000000000002
d) NaN
c) 0.010000000000000002
console.log(0.1 + 0.2);
a) 0.30000000000000004
b) 0.3
c) 2
d) NaN
a) 0.30000000000000004
**
— это аналог Math.pow возводим 0.1
в квадрат — должно получиться 0.01
, но в JS (как и во многих других языках) есть известная проблема с точностью операций при работе с числами с плавающей запятой [2]. Будет 0.010000000000000002
Связано это с тем что в двоичной системе получается бесконечная дробь т.к. под число в 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.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
c) 2 3
onclick добавит обработчик console.log(1)
, но в следующей строчке мы перетираем его новой функцией и остается только console.log(2)
. onclick
— это DOM свойство оно всегда одно
События сработают в том порядке в котором навешены, сначала будет выведено 2 потом 3.
Если бы мы несколько раз делали addEventListener
то сработал бы каждый из них, т.к. хендлеры добавляют события в очередь.
Секция вопросов про различные 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) ошибка
b) 2, 2
(() => {
'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) ошибка
c) 2, 3
Во всех вопросах выше проверяется знание метода defineProperty
а конкретнее настройки writable
. Если она установлена в false
то запрещается менять значения ключу переданному вторым параметром в defineProperty
. Разница только в том что без строгого режима — use strict
движок притворится что все хорошо, но значение не поменяет, а в в строгом режиме будет ошибка.
let x = 5;
console.log(x++);
a) 5
b) 6
c) '5++'
d) ошибка
a) 5
const a = 5;
console.log(a++);
a) 5
b) 6
c) '5++'
d) ошибка
d) ошибка
При использовании постфиксной формы инкримента возвращается значение до увеличения.
А при префиксной после, т.е. console.log(++5)
распечатало бы 6
const
нельзя перезаписывать, а т.к. 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
b) 3
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
a) NaN
Делить бесконечность на бесконечность и вычесть бесконечность из бесконечности нельзя т.к. с математической точки зрения получается неопределенность, то же самое произойдет при умножении Infinity
и 0
ошибок математические операции не вызывают — будет 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
b) -10
Исчерпывающее описание с MDN [3]:
Если функция parseInt встречает символ, не являющийся числом в указанной системе счисления, она пропускает этот и все последующие символы (даже, если они подходящие) и возвращает целое число, преобразованное из части строки, предшествовавшей этому символу. 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
d) 12 12 18
Proxy
перехватывает все обращения к объекту. В данном случае мы проксируем только get
метод и всегда возвращаем 12
независимо от того, к какому полю объекта мы обращаемся. При этом set
мы не трогаем, и при обращении к прокси значение в объекте будет заменено.
let arr = [];
arr[1] = 1;
arr[5] = 10;
console.log(arr.length);
a) 1
b) 5
c) 6
d) 10
c) 6
let arr = new Array(3);
console.log(arr[1]);
a) undefined
b) 1
c) 3
d) ошибка
a) undefined
Когда мы создаем Array
с одним числовым аргументом — он означает длину массива. Массив при этом создается пустой, все значения undefined
. То же самое произойдет если создать обратиться к несуществующему полю массива. Стоит отметить, что если передать в 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
c) 2
console.log(0 || '' || 2 || undefined || true || false);
a) 0
b) false
c) 2
d) true
c) 2
console.log(2 && '1' && null && undefined && true && false);
a) 2
b) false
c) undefined
d) null
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) ошибка
a) false
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) ошибка
a) true
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) будет ошибка
b) true
При ==
сравниваем по ссылке.
при операции >, >=, <, <=
операнды преобразуются к примитивам и у arrayFoo
вызывается метод valueOf, который должен вернуть примитивное значение arrayFoo
, но он возвращает ссылку на этот же массив. Далее происходит преобразование в примитивное значение вызовом метода toString
, который в свою очередь вернет строковое представление массива в виде "1,2,3,4" сравнит лексикографически два массива и вернет true
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
b) true, true, false
Исчерпывающее объяснение с MDN [4]:
Поведение этого метода (речь об Object.is
) не аналогично оператору ===
. Оператор ===
(также как и оператор ==
) считает числовые значения -0
и +0
равными, а значение Number.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
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) ошибка
a) да
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
c) 1
Ни один из операторов строкой не является, +
приводит к числу. Получается 1 + 0
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
c) ',1'
Вызываем toString
на объекте, при этом 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
b) 7
Тут уже строки после первой конкатенации получаем: '0x11' - '1e1'
. Из-за знака -
приводим все к числу.
0x11
— шестнадцатеричная запись числа в десятичной это 17
.
1e1
— экспоненциальная форма тоже самое что 1 * 10 ** 1
— т.е. просто 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) ошибка
b) 'object'
typeof
всегда возвращает строку, имеет меньший приоритет чем вызов функции, поэтому сначала выполняется функция, а typeof
применяется к возвращаемому ей результату. Объекты Function наследуются от Function.prototype. Спека явно определяет что это объект.
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
c) 2 3
Promise.reject('foo')
.then(() => Promise.resolve('bar'), () => {})
.then((a) => {console.log(a)})
a) foo
b) bar
c) undefined
d) ошибка
c) undefined
Promise.reject
— возвращает промис в rejected состоянии.
Надо вспомнить что then
принимает 2 параметра, onFulfill
и onReject
колбеки. Если происходит ошибка до этого then
, то мы попадаем в onReject
колбек. Если в нем не происходит ошибки то дальше мы попадает в onFulfill
следующего then
. И еще не забываем что () => {}
возвращает не пустой объект, а undefined
, чтобы вернуть пустой объект надо писать так: () => ({})
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
b) 2,1
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
a) 1, 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
b) FUS DAH! RO
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'
c) 1, 2, 3, 'promise1 resolved', 'setTimeout'
Сначала срабатывают все синхронные вызовы, после этого, когда call stack пуст, вызывается то что попало в очередь (асинхронные задачи). Сначала выполняются микротаски — промисы и mutation observer
. В конце текущей таски выполняются все микротаски, в связи с этим микротасками можно заблокировать event loop, после того как таска завершена в браузере происходит рендеринг. После этого выполняются макро таски — таймауты.
Это очень упрощенный пример, более подробно я бы советовал посмотреть выступление Михаила Башурова [5]
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
и только после этого нужно продолжить
выполнение асинхронной функции. Спека [6]. Для более подробного понимания, почему это так, советую почитать отличную статью на v8.dev [7]
Автор: tapo4ki
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/300824
Ссылки в тексте:
[1] тут: https://habr.com/company/hh/blog/431492/
[2] числами с плавающей запятой: https://ru.wikipedia.org/wiki/%D0%A7%D0%B8%D1%81%D0%BB%D0%BE_%D0%B4%D0%B2%D0%BE%D0%B9%D0%BD%D0%BE%D0%B9_%D1%82%D0%BE%D1%87%D0%BD%D0%BE%D1%81%D1%82%D0%B8
[3] MDN: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/parseInt#Description
[4] MDN: https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description
[5] выступление Михаила Башурова: https://habr.com/company/oleg-bunin/blog/417461/
[6] Спека: https://tc39.github.io/ecma262/#await
[7] отличную статью на v8.dev: https://v8.dev/blog/fast-async
[8] Источник: https://habr.com/post/431698/?utm_campaign=431698
Нажмите здесь для печати.