Analysis of issues at the hh.ru stand at # HolyJS18

    We tried to do something interesting and unusual for you. I really hope that we have succeeded. We did not want to leave you without answers and explanations why this is so. Let's figure it out.


    First I want to remind you how the competition took place, there were 4 rounds with 15 questions about JS, 1 out-of-competition round with 15 questions about React and a final with 10 questions.


    image


    Under the cut - analysis of the tasks of the first 4 rounds.


    This is the second part of our analysis.
    Analysis of questions about React here


    How did we all do it? We decided that we need to generate about 80-90 questions so that there is a stock to choose from. After that, we divided everything into topics:


    • browser events
    • various APIs (Array, Set, defineProperty, etc.),
    • attentiveness
    • working with fractional numbers
    • hoisting
    • event loop
    • cast
    • typeof
    • logical (with logical AND and OR)

    After that distributed questions on 4 rounds. We tried to make all the tours the same in complexity, for this we made several visits by passing these tests and determining where the questions were simpler, where it was more difficult and replaced the outstanding questions with more appropriate ones. And we did in each round about the same number of questions on a particular topic. As a result, it turned out that in different tours there were similar, but not identical questions.


    Because of this, sorting out the tours seems not very convenient because there will be a lot of duplicate explanations, I suggest looking at them by topic. Let's start with the simplest.


    Questions on attentiveness:


    What will be displayed in the console?


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

    Answer + parse

    d) 0 1 2
    There is between the numbers ,, and not .if you format the question like this:
    console.log(0, 1 + 0, 2);everything will become clear


    What will be displayed in the console?


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

    Answer + parse

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


    What will be displayed in the console?


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

    Answer + parse

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


    Questions about working with fractional numbers:


    What will be displayed in the console?


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

    Answer

    c) 0.010000000000000002


    What will be displayed in the console?


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

    Answer + parse

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


    We turn to the questions a little more difficult.


    Events in the browser:


    There is an event handler on the element. What values ​​inside this handler will always be the same?


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

    Answer + parse

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


    What will this code get by clicking on a 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  

    Answer + parse

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


    Questions section about various APIs


    defineProperty:


    What will this code output?


    (() => {  
        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) ошибка  

    Answer

    b) 2, 2


    What will this code output?


    (() => {
        '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, ошибка

    Answer

    d) 2, ошибка


    What will this code output?


    (() => {  
        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) ошибка 

    Answer + parse

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


    increment:


    What will this code output?


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

    Answer

    a) 5


    What will this code output?


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

    Answer

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


    Set:


    What will this code output?


    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) ошибка  

    Answer

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


    What will this code output?


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

    Answer

    b) 3


    What will this code output?


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

    Answer

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


    What will this code output?


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

    Answer

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


    Questions about Spread:


    What will this code output?


    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) ошибка 

    Answer

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


    What will this code output?


    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) ошибка  

    Answer + parse

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


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

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


    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 

    Answer + parse

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


    arrays:


    What will this code output?


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

    Answer

    c) 6


    What will this code output?


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

    Answer + parse

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


    logical operations &&, ||, ==etc .:


    What will this code output?


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

    Answer

    c) undefined


    What will this code output?


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

    Answer

    c) 2


    What will this code output?


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

    Answer

    c) 2


    What will this code output?


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

    Answer

    d) null


    What will this code output?


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

    Answer + parse

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


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


    functionf(userParams) {
        var params = userParams || defaultParams;
    }

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


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

    array comparison:


    What will this code output?


    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) ошибка  

    Answer

    a) false


    What will this code output?


    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]   

    Answer

    d) [true, true, true]


    What will this code output?


    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) ошибка  

    Answer

    a) true


    What will this code output?


    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) будет ошибка  

    Answer + parse

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


    What will this code output?


    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  

    Answer + parse

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


    Questions about hoisting:


    What will this code output?


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

    Answer

    c) ошибка


    What will this code output?


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

    Answer

    d) 3


    What will this code output?


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

    Answer

    b) undefined


    What will this code output?


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

    Answer

    b) undefined 0


    Does the function call work?


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

    Answer

    a) да


    What will this code output?


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

    Answer + parse

    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]());

    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    What will this code output?


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

    Answer + parse

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


    typeof:


    What will this code output?


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

    Answer

    b) 'string'


    What will this code output?


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

    Answer + parse

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


    event loop:


    Let's start with 2 questions about promises.


    What will this code output?


    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  

    Answer

    c) 2 3


    What will this code output?


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

    Answer + parse

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


    order of tasks.


    What will this code output?


    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  

    Answer

    b) 2,1


    What will this code output?


    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  

    Answer

    b) after, in promise, in timeout


    What will this code output?


    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  

    Answer

    a) 1, 2


    What will this code output?


    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  

    Answer

    b) FUS DAH! RO


    What will this code output?


    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' 

    Answer + parse

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


    And the last question promise against await


    What will this code output?


    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


    Answer + parse

    c) 2 3 1


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


    Also popular now: