← Academy Blog

Марафон підготовки 2022

Translated into:

Стартував марафон підготовки до тестів - JS/Mobile трек - Binary Studio Academy 2022! Щотижневі питання з відповідями та пояснення чекають на нашому блозі.

  • [JS/Mobile] Що виведеться у консоль у цих двух випадках?

    a)

    for (var i = 0; i < 5; i++) {
      setTimeout(() => console.log(i), 1);
    }

    b)

    for (let i = 0; i < 5; i++) {
      setTimeout(() => console.log(i), 1);
    }

    a) a) 0 1 2 3 4; b) 5 5 5 5 5
    b) a) 5 5 5 5 5; b) 0 1 2 3 4
    c) a) 5 5 5 5 5; b) 5 5 5 5 5

    Розгорнути правильну відповідь з поясненням

    Для початку потрібно розібратися у чому різниця між var та let. Область видимості змінної, оголошеної через var, це її поточний контекст виконання. Який може обмежуватися функцією чи бути глобальним, для змінних, оголошених поза функцією. Але у var є одна слабкість. Розглянемо її на прикладі нижче.

    var greeting = 'say hi';
    var times = 4;
    
    if (times > 3) {
      var greeting = 'say Hello instead';
    }
    
    console.log(greeting); // "say Hello instead"

    Як видно, оскільки умова times > 3 повертає значення true, значення змінної greeter перевизначається як 'say Hello instead'. Якщо ви несвідомо використаєте greeting в інших ділянках коду, ви отримаєте непередбачуванний результат. Це може викликати безліч помилок у вашому коді. Ось чому виникає потреба в let та const.

    let має блокову область видимості. Блок – це фрагмент коду, обмежений фігурними дужками {}. Все, що знаходиться всередині фігурних дужок, відноситься до блоку. Таким чином, змінна, оголошена в блоці через let, буде доступна лише усередині цього блоку. Давайте тепер розглянемо ще один приклад.

    let times = 4;
    
    if (times > 3) {
      let greeting = 'say Hello';
      console.log(greeting); // "say Hello"
    }
    
    console.log(greeting); // ReferenceError: greeting is not defined

    З цього прикладу видно, що спроба використати greeting поза блоком (фігурних дужок, у межах яких змінна була визначена) поверне помилку, через те що let має блокову область видимості.

    Тож повернемося до завдання. Оскільки setTimeout виконується асинхронно, а одна мілісекунда це дуже довгий час для комп’ютера, то переданний колбек викличеться вже після виконання циклу.

    У випадку a змінна i оголошується за допомогою ключового слова var, тому вона є глобальною. Вона буде перезаписуватися кожну ітерацію, а коли викличеться setTimeout, буде мати своє останнє значення - 5, тож у консоль послідовно виведеться 5 5 5 5 5.

    У випадку b змінна i вже оголошена через let і має блокову область видимості. Тобто вона є доступною лише в області видимості циклу for. Під час кожної ітерації змінна матиме нове значення, а після виконання коду у консоль виведеться 0 1 2 3 4.

    Отже правильна відповідь a) 5 5 5 5 5; b) 0 1 2 3 4

  • [JS/Mobile] Що виведе цей код?
    const obj = {
      a: 1,
      b: function () {
        return this.a;
      },
      c: () => this.a,
    };
    console.log(obj.b());
    console.log(obj.с());

    a) 1 1
    b) undefined undefined
    c) 1 undefined
    d) null null

    Розгорнути правильну відповідь з поясненням

    Перед тим, як розібрати цей приклад, ми повинні зрозуміти, що таке точка виклику, контекст та стрілочна функція.

    Точка виклику — це місце в коді, де була викликана функція (не там, де вона оголошена). Ми повинні досліджувати точку виклику, щоб відповісти на запитання: на що ж вказує this?

    Загалом пошук точки виклику виглядає так: "знайти звідки викликається функція", але це не завжди так легко, оскільки певні шаблони кодування можуть ввести в оману щодо справжньої точки виклику.

    Важливо поміркувати над стеком викликів (стеком функцій, які були викликані, щоб привести нас до поточної точки виконання коду). Точка виклику, яка нас цікавить, знаходиться у виклику перед поточною виконуваною функцією.

    Контекст виконання (execution context) — це концепція, що описує оточення, у якому виконується код. Код завжди виконується всередині якогось контексту

    Стрілочна функція (arrow function) — це, простий і лаконічний синтаксис для створення функцій, який часто краще ніж звичайний Function Expression

    Простими словами

    let func = (arg1, arg2, ...argN) => expression;

    те саме, що й

    let func = function (arg1, arg2, ...argN) {
      return expression;
    };

    Проте крім синтаксису написання є також важливі відмінності про які ми поговоримо пізніше, але все по черзі.

    Тепер розглянемо як точка виклику впливає на те, що буде у this під час виконання функції.

    Взагалі існує 4 правила прив’язки для цього:

    • прив’язка за замовчуванням;
    • неявна прив’язка
    • явна прив’язка
    • жорстка прив’язка

    У нашому прикладі ми розглядаємо неявну прив’язку. Розберімо наступний код:

    function foo() {
      console.log(this.a);
    }
    
    var obj = {
      a: 2,
      foo: foo,
    };
    
    obj.foo(); // 2

    По-перше, відзначимо спосіб, яким була оголошена функція foo(), а потім пізніше додана як посилання в obj. Незалежно від того чи була foo() спочатку оголошена в obj або додана пізніше як посилання (як у наведеному вище коді), ні в тому, ні в іншому випадку функція насправді не "міститься" в об'єкті obj.

    Однак, точка виклику використовує контекст obj, щоб посилатися на функцію, тому можна сказати, що об'єкт obj "володіє" або "містить" посилання на функцію в момент виклику функції.

    Яку назву ви б не вибрали для цього шаблону, коли викликається foo(), їй передує об'єктне посилання на obj. Коли є об'єкт контексту для посилання на функцію, правило неявної прив'язки говорить про те, що саме цей об'єкт і слід використовувати для прив'язки this до виклику функції.

    Оскільки obj є this для виклику foo(), this.a – синонім obj.a.

    Виходячи з цього можемо дати відповідь на першу частину нашого завдання

    const obj = {
      a: 1,
      b: function () {
        return this.a;
      },
      c: () => this.a,
    };
    console.log(obj.b()); // 1

    Як ви гадаєте чи отримаємо ми той самий результат, коли напишемо console.log(obj.c());? Якщо ваша відповідь ні — то ви цілком праві.

    Справа у контексті. На відміну від інших функцій, стрілочні функції не мають власного контексту. На практиці це означає, що вони наслідують сутності this та arguments від батьківських функцій. У анонімної функції b() є власний контекст, який вона перехоплює у obj. А у c() стрілочна функція запам’ятала свій контекст у момент створення і відповідно при виклику цієї функції вона буде посилатися на нього. Таким чином ми отримуємо window.

    Однак ви можете поставити запитання «Але чому в момент створення контексту було window, а не об'єкт, всередині якого вона була оголошена як метод?». Стрілочна функція в момент створення шукає найближчий до неї контекст і запам'ятовує його, а він у нас такий самий як і у разі простого присвоєння всередині obj.

    Підсумовуючи усе, що написано вище виділимо дві основних тези.

    Теза 1 Для функцій, оголошених через function f( ) { }, this обчислюється в момент виклику і дорівнює об'єкту перед точкою. Якщо такого об'єкта немає — тоді this буде вказувати на глобальний контекст (window). Проте це не стосується суворого (strict) виконання.

    Теза 2 Для стрілочних функцій this визначається в момент їх створення і більше ніколи не змінюється

    Отже, правильна відповідь:

    const obj = {
      a: 1,
      b: function () {
        return this.a;
      },
      c: () => this.a,
    };
    console.log(obj.b()); // 1
    console.log(obj.с()); // undefined
  • [JS/Mobile] ГалереяWeekly Challenge

    Імплементнути логіку, яка дасть змогу при кліку на слайд робити його активним(додавати клас active).

    Slider

    Source Code

    Розгорнути правильну відповідь з поясненням
  • [JS/Mobile] Чому буде дорівнювати змінна someVariable?
    function func() {
      return this;
    }
    
    const outerObj = {
      value: 10,
      innerObj: {
        value: 20,
        method: func,
      },
    };
    
    const target = outerObj.innerObj;
    const someVariable = target.method();

    a) window
    b) outerObj
    c) innerObj
    d) undefined

    Розгорнути правильну відповідь з поясненням

    Для того, щоб дати правильну відповідь на це запитання, нам необхідно хоча б трохи знати як працює ключове слово this в JavaScript, тому зараз ми це коротко розглянемо.

    Що таке this?

    Як правило, метод об'єкта потребує доступу до інформації, яка зберігається в об'єкті, щоб виконати з нею будь-які дії (відповідно до призначення методу).

    Наприклад, коду всередині user.sayHi() може знадобитися ім'я користувача, яке зберігається в об'єкті user.

    Для доступу до інформації всередині об'єкта метод може використовувати ключове слово this.

    Наприклад:

    const user = {
      name: 'Tanya',
      sayHi: function () {
        return this.name;
      },
    };
    
    user.sayHi(); // 'Tanya'

    Тут під час виконання коду user.sayHi() значенням this буде user (посилання на об'єкт user).

    Використання this дозволяє створювати універсальні функції, які б корректно працювали у якості методів для різних об’єктів. У цьому сила this - значення цього ключового слова обчислюється під час виконання коду і залежить від контексту.

    Зрозуміти, чому дорівнює this у тому чи іншому випадку досить просто: this - це об’єкт перед точкою. Наприклад, при виклику obj.f() значення this всередині f дорівнює obj.

    function sayHi() {
      console.log(this.name);
    }
    
    const user = {
      name: 'Tanya',
    };
    
    const admin = {
      name: 'Misha',
    };
    
    user.f = sayHi;
    admin.f = sayHi;
    
    // виклики функції, наведені нижче, мають різне значення this
    // "this" всередині функції є посиланням на об'єкт, який вказано "перед точкою"
    user.f(); // Tanya (this == user)
    admin.f(); // Misha (this == admin)
    
    admin['f'](); // Misha (неважливий спосіб доступу до методу – через точку або квадратні дужки)

    Якщо ж ми викличемо функцію без об’єкта перед точкою, то отримаємо undefined (якщо працюємо у режимі "use strict"), або глобальний об’єкт (якщо режим "use strict" не активовано). У браузерному середовищі глобальний об’єкт - це об’єкт window.

    function sayHi() {
      console.log(this);
    }
    
    sayHi(); // undefined або глобальний об’єкт

    Є ще деякі способи явно вказати функції, який саме об’єкт вона має вважати за this під час свого виконання. Для цього використовуються методи call, apply та bind.

    Тепер перейдемо до завдання

    З самого початку ми маємо функцію func яка просто повертає this. Ми вже знаємо, що this може бути різним і його значення буде залежати від того, як ми викличемо цю функцію.

    function func() {
      return this;
    }

    Далі ми створюємо об’єкт, у якого є вкладений об’єкт всередині. І вже цьому вкладеному об’єкту ми додаємо нашу функцію як метод.

    const outerObj = {
      value: 10,
      innerObj: {
        value: 20,
        method: func,
      },
    };

    Вкладений об’єкт - це повністю повноправний об’єкт як і будь який інший, він також може мати свої методи і властивості. Наш код міг би виглядати і так:

    const innerObj = {
      value: 20,
      method: func,
    };
    
    const outerObj = {
      value: 10,
      innerObj: innerObj,
    };

    Далі ми створюємо нову змінну target і вказуємо, що вона дорівнює нашому вкладеному об’єкту. Добре, тепер в нас є 2 змінні, які вказують на один і той самий вкладений об’єкт.

    const target = outerObj.innerObj;

    Тобто в нашому випадку target - це той самий innerObj, а не його копія. Просто тепер в нас 2 шляхи до його властивостей та методів - outerObj.innerObj та target. Подивіться уважно на цей код, якщо не дуже розумієте про що йде мова:

    const outerObj = {
      value: 10,
      innerObj: {
        value: 20,
        method: func,
      },
    };
    
    const target = outerObj.innerObj;
    
    target.value = 0;
    console.log(outerObj.innerObj.value); // 0
    // тому що target - просто посилання на той самий об'єкт innerObj, а не його копія

    Добре, з цим розібрались, що в нас далі?

    const someVariable = target.method();

    А далі ми просто викликаємо target.method(), де method - це наша функція func яка повертає this. this дорівнює об’єкту перед точкою, у цьому випадку об’єкт перед точкою - це target. Але, як ми вище зрозуміли, target та innerObj - це не просто два однакових об’єкти або дві копії - це один і той самий об’єкт. Тому в нас someVariable, target та outerObj.innerObj - це все посилання до одного об’єкта у пам’яті.

    Правильна відповідь - innerObj.

  • [JS/Mobile] Створити випадаючий блок з іконками додатків як в ГуглаWeekly Challenge

    Google Bar

    Головні етапи виконання завдання:

    • зробити кнопку, яка буде відкривати/закривати блок з додатками;
    • зробити сам блок з іконками додатків;
    • реалізувати кастомний скроллбар для цього блоку;
    • допилити стилі щоб виглядало усе як в оригіналі.

    Чим меньше ваша кнопка та блок відрізняються від оригинального - тим крутіше вам вдалося виконати завдання! В ідеалі вони можуть бути взагалі ідентичні, з усіма стилями та hover-еффектами. Але якщо вам вдастся виконати лише частину з цих завдань - це вже буде дуже корисно для вас!

    Що можна і треба робити для виконання завдання:

    • поклацати цю менюшку в Гуглі, подивитися як вона працює;
    • використати dev-tools, щоб заглянути всередину - що як зроблено, як виставляються зображення для елементів меню, як картинки звідти або навіть шматочки CSS можна перевикористати для виконання завдання;
    • гуглити - як зробити кастомний чекбокс, як зробити кастомний скроллбар і усе інше що вам буде потрібно;
    • використовувати тільки HTML, CSS та картинки.

    Чого НЕ треба робити:

    • не використовувати JS чи будь-які CSS бібліотеки.

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

    • загальна розмітка та стилі;
    • сам блок з іконками додатків;

    У таком разі треба буде лише:

    • зробити кнопку, яка буде відкривати/закривати блок з додатками;
    • доробити позиціонування блоку та щоб він реагував на кнопку;
    • додати кастомний скроллбар для цього блоку;
    • додати hover, focus стилі для кнопки.
    Розгорнути правильну відповідь з поясненням
  • [JS/Mobile] Який рядок пропущенно?

    Юний програміст займався рефакторингом коду. Він вирішив обгорнути функцію сторонньої бібліотеки, що приймає колбек, у проміс, проте зробів помилку. Вкажіть, який рядок пропущенно:

    // before
    someFunc((err, res) => {
      if (err) {
        console.log(err);
      } else {
        console.log(res);
      }
    });
    
    // after
    1 const someFuncPromise = () =>
    2   new Promise((resolve, reject) => {
    3     //
    4     someFunc((err, res) => {
    5       if (err) {
    6         console.log('Error');
    7         //
    8       }
    9       resolve(res);
    10      console.log('Sucess');
    11      //
    12    });
    13  });

    a) return cb() - 7-ий рядок
    b) if - 3-ий рядок
    c) return reject(err) - 7-ий рядок
    d) else - 7-ий рядок
    e) return - 11-ий рядок

    Розгорнути правильну відповідь з поясненням

    Щоб розв’язати це завдання, по-перше, потрібно розібратися як повинен працювати колбек переданний в функцію сторонньої бібліотеки. Він приймає два параметри err та res, потім іде if блок, який перевіряє чи є умова істинною. У JavaScript усі значення є істинними, якщо вони не визначені як хибні. Тобто усі значення, окрім false, 0, "", null, undefined і NaN істинні. Тож у нашому випадку якщо ми отримаємо помилку, то вона виведеться в консоль. Якщо ж помилки немає, то виконання перейде до блоку else і тоді в консоль виведеться значення res.

    Під час рефакторингу функція була обгорнута у проміс, який має прийняти наприкінці виконання один зі станів: fulfilled (за допомогою виклику resolve()) або rejected (за допомогою reject()).

    Розглянемо детальніше виконання цього коду.

    • Якщо у нас немає помилки, то перша умова if (err) поверне false і тоді виконання перейде до наступного блоку і виконається resolve промісу і залогується console.log('Sucess') .
    • У випадку помилки умова if (err) поверне true. Через це у консоль виведеться "Error" , але на виконання фунції це ніяк не вплине — проміс благополучно зарезолвиться та спрацює “успішний” console.log.

    Отже, по-перше, нам слід дотриматись логіки роботи з промісами та викликати reject(err)  у разі помилки.

    А по-друге, слід не забувати, що функції resolve та reject змінюють стан проміса, але не зупиняють його, через це у консоль все одно виведеться повідомлення "Success".

    Отож повна правильна видповідь тут буде така: return reject(err) - 7-ий рядок.

  • [JS/Mobile] Пошуку персонажів Гри престолів v2Weekly Challenge

    Сьогодні ми продовжемо розвивати функціонал сторінки з минулого тижня.

    • Наразі треба надати користувачам можливість обирати улюблених персонажів.
    • Також для більш пріємного користувацького інтерфейсу давайте додамо лоадер на час завантаження даних про персонажів.

    Стартер проекту ви знайдете тут.

    Розгорнути правильну відповідь з поясненням
  • [JS/Mobile] Скільки одиниць виведеться в консоль?
    const func = (x) => {
      switch (isNaN(x) || x) {
        case null: {
          console.log(1);
          break;
        }
        case undefined: {
          console.log(1);
        }
        case NaN: {
          console.log(1);
          return;
        }
        case false: {
          console.log(1);
        }
        case true: {
          console.log(1);
        }
        default:
          break;
      }
    };
    
    func();

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

    Розгорнути правильну відповідь з поясненням

    Для того, щоб правильно відповісти на це питання необхідно розуміти як працювати з оператором switch, як передаються параметри функції а також як працюють функція isNaN та оператор або (||).

    Спочатку варто зрозуміти яке ж значення буде в параметрі x. Оскільки при виклику функції не було передано жодних параметрів то буде використано значення за замовчуванням - undefined. Це можна легко перевірити виконавши наступний шматок коду.

    const f = (arg) => console.log(arg === undefined);
    
    f();

    Далі, варто розуміти як працює оператор switch. Цей оператор оцінює значення яке йому надається, та виконує код пов'язаний з відповідним case та, що важливо пам'ятати, з усіма наступними конструкціями. У цьому можна легко переконатись виконавши наступний код:

    switch (2) {
      case 1:
        console.log('1');
      case 2:
        console.log('2');
      case 3:
        console.log('3');
      default:
        console.log('default');
    }

    Таким чином у консоль буде виведено:

    2
    3
    default

    У javascript є 3 механізми, які зупиняють виконання усіх case конструкцій, це: break, return, throw.

    • break - зупинить виконання усіх наступних case конструкцій, і продовжить виконання функції.
    • return - зупинить виконання усіх наступних case конструкцій, і поверне значення з функції.
    • throw - зупинить виконання усіх case конструкцій і кине помилку.

    Так, якшо ми перепишемо код вище з використанням break ми отримаємо зовсім інший результат:

    switch (2) {
      case 1:
        console.log('1');
        break;
      case 2:
        console.log('2');
        break;
      case 3:
        console.log('3');
        break;
      default:
        console.log('default');
    }

    Буде виведено тільки одна цифра:

    2

    Наступним важливим кроком для розв'язання задачі має бути визначення значення, яке поверне функція isNaN. Щоб це зрозуміти спершу необхідно розібратись, що таке NaN. NaN - пердставляє значення яке не вдалось перетворити у число (Not-a-Number), такі випадки можуть траплятись коли:

    • Конвертується значення, яке не може бути презентовано числом, у число (Number('foo'))
    • Виконується матемтична операція результатом якої не є реальне число (Math.sqrt(-1))
    • Оператором математичної операції є NaN (7 * NaN)
    • Результат виразу є невизначеним (0 * Infinity)
    • Будь-яка операція, яка включає строку і не є операцією додавання ('foo' / 5)

    Функція isNaN конвертує передане в неї значення у число і перевіряє чи це значення є NaN.

    console.log(isNaN('55')); // false
    console.log(isNaN(undefined)); // true

    Останнє, що потрібно знати для розв'язання цієї задачі це поведінку оператора або - ||. Цей оператор повертає друге значення лише тоді коли переше - falsy.

    console.log(false || 'shown'); // 'shown'
    console.log(true || 'not shown'); // true

    Повертаючись до задачі, ми можемо сказати що у функції параметр x має значення undefined. Оскільки це значення не може бути конвертованим у число, то функція isNaN поверне true. Отже оператор || теж поверне true оскільки це значення є правдивим. А це означає, що виконається констркуція

        case true: {
          console.log(1);
        }

    а оскільки немає жодного оператора який би зупиняв виконання case конструкцій, то також виконається і default(Хоча він нічого не виводить у даному завданні). Отже у консоль буде виведено:

    1

    Правильна відповідь - 1.

  • [JS/Mobile] Завданням для цього тижня буде зробити додаток для пошуку персонажів Гри престолів.Weekly Challenge

    Дані про персонажів будемо брати з цього API.

    View Example Example

    Щоб спростити вам завдання ми підготували заготовку де вже майже все готово, залишилося зробити отримання персонажів з API. Клонуйте репозиторій із заготовкою і читайте опис.

    Розгорнути правильну відповідь з поясненням