{
  "assessmentTests": {
    "javascript_test": {
      "name": "Тест JavaScript",
      "desc": "30 практичних питань про основний синтаксис та приведення типів, функції/область видимості/замикання та this, об'єкти/масиви та структури даних, та цикл подій/async/прототипи — дізнайтеся, чи відповідає ваше знання JavaScript тому, що роботодавець має на увазі під компетентністю в JavaScript.",
      "recommendation": "Ваш профіль навичок JavaScript",
      "results": {
        "beginner": {
          "name": "Початківець",
          "desc": "Ви можете писати функціональні функції та об'єкти, але питання, які ви пропустили, групуються навколо приведення типів та семантики посилань, а не синтаксису — == тихо перетворює типи перед порівнянням, призначення масиву, яке ділить один об'єкт замість копіювання його, чому NaN ніколи не дорівнює самому собі. Жодне з цього не про те, щоб бути поганим у JavaScript; це специфічні правила, які спантеличують людей, які дізналися мову, написавши код, який випадково працював, а не навчившись тому, як насправді поводяться значення та посилання. Вони важливі на роботі, тому що кожне з них — це місце, де код працює без помилки й все одно робить неправильне.",
          "recommendation": "Почніть з трьох речей у такому порядку: чому == приводить типи перед порівнянням, тоді як === цього не робить; чому призначення одного масиву або об'єкта новій змінній копіює посилання, а не значення; і як var піднімається та ініціалізується на undefined, тоді як let та const залишаються у тимчасовій мертвій зоні до виконання їхнього оголошення. Посібник MDN з JavaScript охоплює всі три з придатними для запуску прикладами."
        },
        "intermediate": {
          "name": "Середній рівень",
          "desc": "Ви комфортно справляєтеся з повсякденним кодом програми — функціями, методами масивів, прямолінійним керуванням потоком — і не скупотитесь на рутинну роботу або невелику функцію. Розрив між тут і Розширеним переважно про те, що відбувається, коли правила область видимості та синхронізації JavaScript взаємодіють: var, захоплений усередині зворотних викликів циклу, які всі читають одне й те саме кінцеве значення замість того, що ви очікували; this, який тихо припиняє вказувати на об'єкт, коли метод відділяється від нього; розширена копія, яка виглядає глибокою, але копіює лише один рівень. Це вид помилок, які проходять швидке прочитання й з'являються лише за певних умов виконання.",
          "recommendation": "Зосередьтеся на тому, як область видимості та this насправді розв'язуються у момент виклику, а не у момент визначення: чому цикл for на основі var дає кожному зворотному виклику одне й те саме кінцеве значення лічильника, тоді як let цього не робить; як this визначається способом виклику функції, а не місцем, де вона написана; і чому розширення або Object.assign копіює лише верхній рівень об'єкта. Потім цикл подій, оскільки це спантеличує людей, які вже знають обіцянки індивідуально."
        },
        "advanced": {
          "name": "Розширений",
          "desc": "Це рівень, який більшість оголошень про роботу означає під «сильним JavaScript». Ви читаєте ланцюг filter/map/reduce та можете точно передбачити, що він повертає без зміни вихідного масиву, ви знаєте, чому синхронний console.log завжди перемагає мікротаск, який завжди перемагає setTimeout, і ви використовуєте Promise.all, знаючи, що він відхиляється в момент, коли хоча б одна обіцянка відхиляється, замість чекання на решту. Те, що відділяє цей діапазон від вершини, — це прототип та об'єктна модель роботи: як ланцюг прототипів розв'язує успадковану методу проти hasOwnProperty, і що приватне поле класу насправді приховує.",
          "recommendation": "Поштовхніться у частини, які захищають стан, який також торкається іншого коду: як ланцюг прототипів JavaScript розв'язує пошук методів, які не на самому екземплярі; що приватні #fields класу насправді приховують від зовнішнього доступу; і як JSON.stringify по-різному обробляє undefined та функції всередину простого об'єкта та всередину масиву. Матеріал MDN про цикл подій та ланцюг прототипів — природний наступний крок для обох."
        },
        "expert": {
          "name": "Експерт",
          "desc": "Ви набрали найвищі бали в кожному розділі — основний синтаксис, типи та приведення; функції, область видимості, замикання та this; об'єкти, масиви та структури даних; та async, цикл подій, прототипи та ідіоми. Практично це означає, що вам можуть дати сторонню функцію та пояснити, чому вона повертає те, що повертає, а не лише те, що синтаксис говорить, що вона повинна робити, що є складнішою та ціннішою навичкою. На цьому рівні сама мова рідко є обмежуючим фактором; обмеження зазвичай — це асинхронне керування потоком або форма даних під ним.",
          "recommendation": "Повернення тепер у дизайні та діагностиці: міркування про умову перегонки між Promise.all та Promise.race, а не повторення їхніх визначень; вибір того, коли замикання є правильним інструментом для приватного стану, а коли приватне поле класу; та читання помилки мікротаск-проти-макротаск упорядкування в журналах виробництва, а не в вікторині. Якщо вас обирають для ролі, опишіть помилку синхронізації, яку ви знайшли — застаралу змінну замикання або асинхронний зворотний виклик поза порядком — замість назив функцій JavaScript; це демонструє міркування, а не просто словник."
        }
      },
      "questions": [
        {"question": "console.log(x);\nvar x = 5;\nЩо це виводить?", "options": [{"icon": "", "label": "5 — оголошення var виконуються негайно, перед будь-яким іншим кодом"}, {"icon": "", "label": "ReferenceError: x не визначена"}, {"icon": "", "label": "undefined — оголошення var піднімаються на вершину їхної області видимості та ініціалізуються на undefined, але призначення виконується на місці, тому перед його виконанням змінна існує, але містить undefined"}, {"icon": "", "label": "null"}]},
        {"question": "console.log(y);\nlet y = 5;\nЩо відбувається?", "options": [{"icon": "", "label": "Це викидає ReferenceError: Неможливо отримати доступ до 'y' перед ініціалізацією — let піднімається на вершину своєї області видимості, але залишається у тимчасовій мертвій зоні до того, як рядок оголошення насправді виконається"}, {"icon": "", "label": "Це виводить undefined, як var"}, {"icon": "", "label": "Це виводить 5"}, {"icon": "", "label": "Це викидає SyntaxError під час аналізу"}]},
        {"question": "const arr = [1, 2, 3];\narr.push(4);\nconsole.log(arr.length);\nЩо це виводить?", "options": [{"icon": "", "label": "4 — const лише запобігає переналаштуванню самого зв'язування arr, а не мутуванню об'єкта масиву, на який він вказує, тому push все ще працює"}, {"icon": "", "label": "Це викидає TypeError, тому що arr є const"}, {"icon": "", "label": "3 — push блокується на const масиві"}, {"icon": "", "label": "undefined"}]},
        {"question": "console.log('' == 0);\nconsole.log('' === 0);\nЩо виводять два рядки?", "options": [{"icon": "", "label": "true потім true"}, {"icon": "", "label": "false потім false"}, {"icon": "", "label": "true потім false — == приводить порожний рядок до 0 перед порівнянням, тоді як === порівнює тип та значення без приведення, і рядок ніколи не суворо не дорівнює числу"}, {"icon": "", "label": "false потім true"}]},
        {"question": "console.log(NaN === NaN);\nconsole.log(Number.isNaN(NaN));\nЩо виводять два рядки?", "options": [{"icon": "", "label": "true потім true"}, {"icon": "", "label": "true потім false"}, {"icon": "", "label": "false потім false"}, {"icon": "", "label": "false потім true — NaN — це єдине значення в JavaScript, яке не дорівнює собі під ===, але Number.isNaN() все рівно правильно його виявляє"}]},
        {"question": "console.log(typeof null);\nЩо це виводить?", "options": [{"icon": "", "label": "\"null\""}, {"icon": "", "label": "\"object\" — давна помилка мови: typeof null повертає \"object\", хоча null є примітивом, і вона збережена для зворотної сумісності з тих пір"}, {"icon": "", "label": "\"undefined\""}, {"icon": "", "label": "\"boolean\""}]},
        {"question": "Яке з наступних є істинним при використанні в умові if?", "options": [{"icon": "", "label": "0"}, {"icon": "", "label": "\"\" (порожний рядок)"}, {"icon": "", "label": "null"}, {"icon": "", "label": "\"0\" — непорожний рядок завжди істинний у JavaScript, навіть коли його вміст — це сам рядок \"0\""}]},
        {"question": "foo();\nfunction foo() { console.log('a'); }\nbar();\nvar bar = function() { console.log('b'); };\nЩо відбувається?", "options": [{"icon": "", "label": "Обидва виклики виводяться, \"a\" потім \"b\""}, {"icon": "", "label": "foo() виводить \"a\"; bar() викидає TypeError: bar не є функцією — оголошення функцій повністю піднімаються з їхнім тілом, але var-присвоєна функція вираз піднімає лише саму var (як undefined), а не призначення"}, {"icon": "", "label": "Обидва виклики викидають ReferenceError"}, {"icon": "", "label": "foo() викидає; bar() виводить \"b\""}]},
        {"question": "function makeCounter() {\n  let count = 0;\n  return function() { count++; return count; };\n}\nconst counter = makeCounter();\nconsole.log(counter());\nconsole.log(counter());\nЩо виводять два рядки?", "options": [{"icon": "", "label": "1 потім 1"}, {"icon": "", "label": "0 потім 1"}, {"icon": "", "label": "undefined потім undefined"}, {"icon": "", "label": "1 потім 2 — повернена функція замикає одну й ту саму змінну count з області видимості makeCounter, і ця змінна зберігається між викликами, а не скидається"}]},
        {"question": "for (var i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nЩо це виводить?", "options": [{"icon": "", "label": "0, 1, 2"}, {"icon": "", "label": "3, 3, 3 — var має область видимості функції, а не блоку, тому всі три зворотні виклики замикають одне й те саме i, яке вже завершило цикл та дорівнює 3 на момент їх фактичного виконання"}, {"icon": "", "label": "0, 0, 0"}, {"icon": "", "label": "undefined, undefined, undefined"}]},
        {"question": "for (let i = 0; i < 3; i++) {\n  setTimeout(() => console.log(i), 0);\n}\nЩо це виводить?", "options": [{"icon": "", "label": "3, 3, 3"}, {"icon": "", "label": "0, 0, 0"}, {"icon": "", "label": "undefined, undefined, undefined"}, {"icon": "", "label": "0, 1, 2 — let створює свіжий зв'язок i для кожної ітерації циклу, тому кожен зворотний виклик замикає власну окрему копію, а не одну спільну змінну"}]},
        {"question": "function greet() { return `Hi, ${this.name}`; }\nconst person = { name: 'Ana' };\nconsole.log(greet.call(person));\nЩо це виводить?", "options": [{"icon": "", "label": "\"Hi, Ana\" — Function.prototype.call викликає функцію з явно встановленим this на його перший аргумент, тому this.name розв'язується проти person"}, {"icon": "", "label": "\"Hi, undefined\" — call() працює лише на функціях, які вже є методами об'єкта"}, {"icon": "", "label": "Це викидає TypeError, тому що greet не є методом person"}, {"icon": "", "label": "\"Hi, ${this.name}\" — call() не оцінює буквальний шаблон"}]},
        {"question": "const timer = {\n  seconds: 0,\n  start: function() {\n    setInterval(() => { this.seconds++; }, 1000);\n  },\n};\nЧому люди переважають функцію стрілку над function() { this.seconds++ } всередину setInterval тут?", "options": [{"icon": "", "label": "Функції стрілок не мають своєї this — вони захоплюють this лексично з функції, що охоплює start, яка є timer (оскільки start була викликана як timer.start()), тому this.seconds правильно посилається на timer.seconds на кожному тиці"}, {"icon": "", "label": "Функції стрілок виконуються швидше, ніж звичайні функції під час виконання"}, {"icon": "", "label": "Функції стрілок автоматично пов'язують this із глобальним об'єктом, який випадає дорівнює timer"}, {"icon": "", "label": "Функції стрілок автоматично перетворюють this на глобальну змінну"}]},
        {"question": "function multiply(a, b) { return a * b; }\nconst double = multiply.bind(null, 2);\nconsole.log(double(5));\nЩо це виводить?", "options": [{"icon": "", "label": "Це викидає помилку, тому що bind вимагає обидва аргументи попереду"}, {"icon": "", "label": "10 — bind повертає нову функцію з цим та будь-якими передніми аргументами, встановленими попереду; виклик double(5) надає залишок аргумент b=5, тому multiply(2, 5) запускається"}, {"icon": "", "label": "NaN"}, {"icon": "", "label": "7, тому що bind додає свої аргументи до виклику, а не замінює їх"}]},
        {"question": "function outer() {\n  let x = 10;\n  function inner() {\n    let x = 20;\n    console.log(x);\n  }\n  inner();\n  console.log(x);\n}\nouter();\nЩо виводять два виклики console.log?", "options": [{"icon": "", "label": "20 потім 20"}, {"icon": "", "label": "20 потім 10 — власна let x inner затінює x outer лише всередину області видимості inner; власна x outer ніколи не торкається"}, {"icon": "", "label": "10 потім 10"}, {"icon": "", "label": "Це викидає ReferenceError, тому що x оголошена двічі"}]},
        {"question": "const a = [1, 2, 3];\nconst b = a;\nb.push(4);\nconsole.log(a.length);\nЩо це виводить?", "options": [{"icon": "", "label": "3 — b є окремою копією a"}, {"icon": "", "label": "undefined"}, {"icon": "", "label": "Це викидає TypeError, тому що a є const"}, {"icon": "", "label": "4 — масиви є об'єктами, тому const b = a копіює посилання, а не масив; мутування b через push також мутує точний той самий масив, на який вказує a"}]},
        {"question": "const original = { name: 'Ana', tags: ['x', 'y'] };\nconst copy = { ...original };\ncopy.name = 'Bea';\ncopy.tags.push('z');\nconsole.log(original.name, original.tags.length);\nЩо це виводить?", "options": [{"icon": "", "label": "'Ana' 2 — розширення глибоко клонує кожен вкладений об'єкт та масив"}, {"icon": "", "label": "'Ana' 3 — розширення — це лише поверхневе копіювання: примітивна властивість, як name, копіюється за значенням, тому переназначення copy.name ніколи не торкається original, але вкладений масив, як tags, копіюється за посиланням, тому push в copy.tags мутує точний той самий масив, на який вказує original.tags"}, {"icon": "", "label": "'Bea' 3 — розширення копіює все за посиланням, тому як з вершини, так і вкладені зміни переносяться назад до original"}, {"icon": "", "label": "'Bea' 2"}]},
        {"question": "const { a: x = 10, b: y = 20 } = { a: 5 };\nconsole.log(x, y);\nЩо це виводить?", "options": [{"icon": "", "label": "undefined undefined"}, {"icon": "", "label": "5 undefined"}, {"icon": "", "label": "10 20"}, {"icon": "", "label": "5 20 — a перейменовується на x та присутня, тому вона зберігає своє значення 5; b перейменовується на y та відсутня, тому вона повертається до її значення за замовчуванням 20"}]},
        {"question": "const [first, ...rest] = [1, 2, 3, 4];\nconsole.log(rest);\nЩо це виводить?", "options": [{"icon": "", "label": "[1, 2, 3, 4]"}, {"icon": "", "label": "[2, 3, 4] — rest збирає кожен залишок елемент після першого в абсолютно новий масив"}, {"icon": "", "label": "2"}, {"icon": "", "label": "[1]"}]},
        {"question": "const nums = [10, 1, 2];\nnums.sort();\nconsole.log(nums);\nЩо це виводить?", "options": [{"icon": "", "label": "[1, 2, 10] — sort завжди упорядковує числа чисельно у порядку зростання за замовчуванням"}, {"icon": "", "label": "[10, 1, 2] — sort не мутує вихідний масив"}, {"icon": "", "label": "[1, 10, 2] — стандартний компаратор Array.prototype.sort перетворює елементи на рядки та порівнює їх лексикографічно, тому \"10\" сортується перед \"2\"; sort також мутує масив на місці, а не повертає новий"}, {"icon": "", "label": "[2, 1, 10]"}]},
        {"question": "const user = { profile: { age: 0 } };\nconsole.log(user.profile?.age ?? 'unknown');\nconsole.log(user.address?.city ?? 'unknown');\nЩо виводять два рядки?", "options": [{"icon": "", "label": "'unknown' потім 'unknown' — ?? розглядає 0 як відсутній, як || робит"}, {"icon": "", "label": "0 потім 'unknown' — ?? повертається лише на null або undefined, а не на інші хибні значення на кшталт 0, тому age (0) зберігається як є; user.address є undefined, тому ?. коротко замикає весь ланцюг до undefined та ?? потім надає 'unknown'"}, {"icon": "", "label": "0 потім undefined"}, {"icon": "", "label": "'unknown' потім 0"}]},
        {"question": "const obj = { a: undefined, b: function(){}, c: NaN, d: [undefined, function(){}, 1] };\nconsole.log(JSON.stringify(obj));\nЩо це виводить?", "options": [{"icon": "", "label": "'{\"a\":null,\"b\":null,\"c\":null,\"d\":[null,null,1]}' — кожне непідтримуване значення стає null скрізь, в об'єктах та масивах однаково"}, {"icon": "", "label": "'{\"a\":undefined,\"b\":undefined,\"c\":NaN,\"d\":[undefined,null,1]}'"}, {"icon": "", "label": "'{\"c\":null,\"d\":[null,null,1]}' — властивості об'єкта, значення яких є undefined або функція, повністю пропускаються, і NaN серіалізується до null, але всередину масиву ці ж непідтримувані значення стають null замість того, щоб бути скинутими, оскільки видалення елемента масиву зсуває кожен пізніший індекс"}, {"icon": "", "label": "Це викидає TypeError, тому що NaN не може бути серіалізовано до JSON"}]},
        {"question": "console.log('1');\nsetTimeout(() => console.log('2'), 0);\nPromise.resolve().then(() => console.log('3'));\nconsole.log('4');\nЯкому порядку ці виводяться?", "options": [{"icon": "", "label": "1, 2, 3, 4"}, {"icon": "", "label": "1, 4, 2, 3"}, {"icon": "", "label": "1, 4, 3, 2 — синхронний код запускається першим (тому 1 та 4 виводяться негайно), потім черга мікротаск повністю спорожніється перед тим, як цикл подій переходить до наступного макротаску, тому 3 виводиться перед 2 зворотного виклику setTimeout"}, {"icon": "", "label": "1, 3, 4, 2"}]},
        {"question": "async function foo() {\n  console.log('a');\n  await null;\n  console.log('b');\n}\nconsole.log('start');\nfoo();\nconsole.log('end');\nЯкому порядку ці виводяться?", "options": [{"icon": "", "label": "start, a, end, b — foo() запускається синхронно до його першого await, тому 'a' логується негайно, коли foo() викликається; await потім призупиняє foo та повертає управління викликаючому, тому 'end' логується перед відновленою 'b', яка запускається пізніше як мікротаск"}, {"icon": "", "label": "start, a, b, end"}, {"icon": "", "label": "start, end, a, b"}, {"icon": "", "label": "a, start, end, b"}]},
        {"question": "Promise.all([\n  Promise.resolve(1),\n  Promise.reject('err'),\n  Promise.resolve(3),\n]).then(r => console.log('ok', r)).catch(e => console.log('fail', e));\nЩо це виводить?", "options": [{"icon": "", "label": "'fail err' — Promise.all відхиляється, як тільки одна з його вхідних обіцянок відхиляється, ігноруючи результати інших, тому обробник catch запускається з причиною цього відхилення"}, {"icon": "", "label": "'ok' [1, 3]"}, {"icon": "", "label": "'ok' [1, undefined, 3]"}, {"icon": "", "label": "Це викидає синхронно та жоден обробник ніколи не запускається"}]},
        {"question": "const fast = new Promise(res => setTimeout(() => res('fast'), 10));\nconst slow = new Promise(res => setTimeout(() => res('slow'), 100));\nPromise.race([fast, slow]).then(v => console.log(v));\nЩо це виводить?", "options": [{"icon": "", "label": "'slow'"}, {"icon": "", "label": "['fast', 'slow']"}, {"icon": "", "label": "'fast' — Promise.race виконується, як тільки перша з його вхідних обіцянок виконується, і таймер 10 мс розв'язується задовго до таймера 100 мс"}, {"icon": "", "label": "Це викидає помилку, тому що race вимагає рівно двох обіцянок"}]},
        {"question": "function Animal(name) { this.name = name; }\nAnimal.prototype.speak = function() { return `${this.name} makes a sound`; };\nconst dog = new Animal('Rex');\nconsole.log(dog.speak());\nconsole.log(dog.hasOwnProperty('speak'));\nЩо виводять два рядки?", "options": [{"icon": "", "label": "'Rex makes a sound' потім true"}, {"icon": "", "label": "undefined потім false"}, {"icon": "", "label": "Це викидає TypeError, тому що dog не має своєї методи speak"}, {"icon": "", "label": "'Rex makes a sound' потім false — speak визначена на Animal.prototype, а не на екземплярі dog; dog знаходить її через ланцюг прототипів, коли вона викликається, але hasOwnProperty перевіряє лише власні властивості екземпляра, а не все успадковане"}]},
        {"question": "class Counter {\n  #count = 0;\n  increment() { this.#count++; return this.#count; }\n}\nconst c = new Counter();\nconsole.log(c.increment());\nconsole.log(c.count);\nЩо виводять два рядки?", "options": [{"icon": "", "label": "1 потім undefined — #count — це приватне поле класу, читаюче та записуюче лише з методів самого класу; c.count (без #) — це абсолютно різна, звичайна властивість, яка ніколи не встановлювалась, тому вона є undefined"}, {"icon": "", "label": "1 потім 0"}, {"icon": "", "label": "1 потім 1"}, {"icon": "", "label": "Це викидає SyntaxError при доступі до c.count"}]},
        {"question": "const items = ['a', 'b'];\nconst label = `Items: ${items.length > 0 ? items.join(', ') : 'none'}`;\nconsole.log(label);\nЩо це виводить?", "options": [{"icon": "", "label": "'Items: ${items.length > 0 ? items.join(\\', \\') : \\'none\\'}'"}, {"icon": "", "label": "'Items: none'"}, {"icon": "", "label": "'Items: a, b' — буквальні шаблони оцінюють будь-який вираз, розміщений всередину ${...}, включаючи повну потрійну, та інтерполюють його результат безпосередньо в рядок"}, {"icon": "", "label": "Це викидає SyntaxError, тому що потрійні не дозволяються всередину буквальних шаблонів"}]},
        {"question": "const nums = [1, 2, 3, 4, 5];\nconst result = nums.filter(n => n % 2 === 0).map(n => n * 10).reduce((sum, n) => sum + n, 0);\nconsole.log(result, nums);\nЩо це виводить?", "options": [{"icon": "", "label": "60 [1, 2, 3, 4, 5] — filter, map та reduce кожен повертає новий масив або значення без мутування вихідного масиву, тому nums не змінений, тоді як result — це остаточна зменшена сума (2 та 4, подвоєні до 20 та 40, просумовані до 60)"}, {"icon": "", "label": "60 [2, 4, 20, 40]"}, {"icon": "", "label": "150 [1, 2, 3, 4, 5]"}, {"icon": "", "label": "60 [20, 40]"}]}
      ],
      "optionOrderVersion": "50fc0eba818f2d72"
    }
  }
}
