programming mentor

ти живий, поки вчишся

Як зрозуміти JavaScript?

2021-03-17 programming mentorJavaScript

Як зрозуміти JavaScript?

Ден Абрамов - відомий розробник з Фейсбука, автор Redux - популярної бібліотеки для керування станом. Мені (programming mentor) дуже сподобалося те, як Ден представляє концепції JavaScript, бо вони надзвичайно близькі до того, як я їх уявляю сам та навчаю інших людей, тому вирішив зробити переклад його статті.

Деякі поняття, такі як, наприклад, замикання я пояснював у своїх відео, додаю їх по ходу тексту. Також даю деякі коментарі від себе там де це доречно. Працюючи на перекладом, я побачив, що бракує ще кількох важливих концепцій, які опустив Ден, то вирішив додати їх в кінці статті від свого імені. Вийшов таким собі розширений переклад, сподіваюся буде корисно всім, хто цікавиться JavaScript - особливо цінною ця інформація буде для тих, хто лише починає вивчати цю мову програмування, але буде в нагоді й для більш досвідчених людей, щоб впорядкувати свої представлення про мову. Також може бути корисно досвідченим розробникам, які використовують інші мови, а з JS стикаються лише епізодично - це дозволить швидко зрозуміти особливості найпопулярнішої на сьогодні мови програмування.

Оригінальна стаття англійською розміщена тут, далі йде текст від імені Дена.

Протягом перших кількох років використання JavaScript я почувався як шахрай. Хоча я міг створювати веб-сайти з фреймворками, чогось не вистачало. Я боявся співбесід на роботу з JavaScript, бо не мав глибокого розуміння основ.

З роками досвіду в мене сформувалася уявна модель JavaScript, яка додала мені впевненості. Тут я ділюсь дуже стислою його версією. Вона побудована як глосарій, на кожну тему кілька речень.

  • Значення (value): Поняття значення є досить абстрактним. Це певна “річ”. Значення для JavaScript - це те, що число означає для математики, або що точка - для геометрії. Коли ваша програма працює, її світ повний значень. Такі числа, як 1, 2 та 420 - це значення, але це стосується і деяких інших речей, наприклад, це речення: "Cows go moo". Однак не все є значенням. Число - це значення, але оператор if - ні. Нижче ми розглянемо кілька різних типів значень.

    Коментар від programming mentor: коли я розповідаю про значення, то говорю що це інформація, дані. І програми по великому рахунку зводяться до маніпуляцій даними. Варто відразу відрізняти код від даних - код це алгоритм, послідовність дій, які застосовуються по відношенню до даних. В процесі виконання програми код зазвичай залишається незмінним, а дані приходять звідкись зовні, зберігаються у змінних і часто змінюються в процесі роботи. Це допомагає початківцям зрозуміти відмінності одного від іншого.

    • Тип значення (type of value): Існує кілька різних “типів” значень. Наприклад, числа, такі як 420, рядки типу "Cows go moo", об’єкти та кілька інших типів. Ви можете дізнатися тип деякого значення, поставивши перед ним typeof. Наприклад, console.log(typeof 2) друкує "number".

      Коментар від programming mentor: користуючись typeof не допустіть типової помилки - typof null повертає "object", а не "null", як би то не дивно звучало. Насправді то помилка в перших імплементаціях мови, яку сам автор JavaScript визнав такою, що її пізно виправляти. Кому цікаво заглибитися в деталі, то за цим посиланням йде детальне пояснення, а в першому коментарі можна зустріти відповідь від Brendan Eich - автора JavaScript.

    • Примітивні значення (primitives): Деякі типи значень є “примітивними”. Вони включають числа, рядки та кілька інших типів. Особливість примітивних значень полягає в тому, що ви не можете створити їх більше або змінити їх будь-яким чином. Наприклад, кожного разу, коли ви пишете 2, ви отримуєте одне і те ж значення 2. Ви не можете “створити” ще 2 у своїй програмі або зробити так, щоб значення 2 “стало” 3. Це також стосується рядків.
    • null та undefined: Це два спеціальні значення. Вони особливі, оскільки з ними багато чого не можна робити - вони часто спричиняють помилки. Зазвичай null означає, що якесь значення навмисно відсутнє, а undefined означає, що значення відсутнє ненавмисно. Рішення, коли використовувати який з варіантів залишається за програмістом. Вони існують, тому що іноді операції краще зазнати невдачі, ніж продовжувати з відсутнім значенням.

      Коментар від programming mentor: Тут трохи незрозумілою є фраза “…тому що іноді операції краще зазнати невдачі, ніж продовжувати з відсутнім значенням”, але тут переклад дослівний “because sometimes it’s better for an operation to fail than to proceed with a missing value”. Найбільш ймовірно, що автор мав на увазі, що у випадку використання null чи undefined можна отримати помилку, наприклад, якщо звертатися до них як до об’єктів, то код згенерує виключну ситуацію. Також варто зазначити, що коли створюється змінна без початкового значення, то JavaScript в неї поміщає undefined.

  • Рівність (equality). Як і "значення", рівність є основним поняттям у JavaScript. Ми говоримо, що два значення рівні, коли вони… насправді, я ніколи не сказав би цього. Якщо два значення рівні, це означає, що вони однакові. Не два різних значення, а одне! Наприклад, "Cows go moo" === "Cows go moo" і 2 === 2, оскільки 2 дорівнює 2. Зверніть увагу, що ми використовуємо три знаки рівності для представлення цієї концепції рівності в JavaScript.

    • Сувора рівність (strict equality): Те саме, що і вище.
    • Рівність за посиланням (equality by reference): Те саме, що і вище.
    • Нестрога рівність (loose equality): О, це особливий випадок! Нестрога рівність - це коли ми використовуємо два знаки рівності (==). Сутності можна вважати нестрого рівними, навіть якщо вони стосуються різних значень, які виглядають схожими (наприклад, 2 та "2"). Цей підхід був доданий до JavaScript з його ранніх днів, і з тих пір є джерелом нескінченної плутанини. Ця концепція не є фундаментальною, але є загальним джерелом помилок. Ви можете розібратися в темі, якщо зануритеся глибоко в неї одного дощового дня, але багато людей намагаються цього уникати.
  • Літерал (literal): Літерал - це коли ви посилаєтесь на значення, буквально записуючи його у свою програму. Наприклад, 2 - це числовий літерал, а "Banana" - це рядковий літерал.
  • Змінна (variable): Змінна дозволяє посилатися на якесь значення за допомогою імені. Наприклад, let message = "Cows go moo". Тепер ви можете писати message, замість того, щоб повторювати одне і те ж речення кожного разу у коді. Пізніше ви можете змінити message, щоб вказати на інше значення, наприклад message = "I am the walrus". Зверніть увагу, що це не змінює саме значення, а лише те місце, на яке вказує message, як “дріт”. Спочатку воно вказувало на "Cows go moo", а тепер вказує на "I am the walrus".

    • Область видимості (scope): Це було б погано, якби у всій програмі могла бути лише одна змінна message. Натомість, коли ви визначаєте змінну, вона стає доступною у частині вашої програми. Ця частина називається “областю видимості”. Існують правила щодо того, як працює область видимості, але зазвичай ви можете шукати найближчі фігурні дужки “{“ та “}” навколо місця, де ви визначаєте змінну. Цей “блок” коду і є областю видимості.
    • Присвоювання (assignment): Коли ми пишемо message = "I am the walrus", ми змінюємо змінну message, щоб вказувати на значення "I am the walrus". Це називається присвоюванням, записом значення до змінної.
    • let vs const vs var: Зазвичай ви хочете let. Якщо ви хочете заборонити присвоєння цій змінній, ви можете використовувати const. (У деяких випадках колеги відрізняються педантичністю і змушують використовувати const, коли є лише одне присвоювання). Уникайте var, якщо можете, оскільки його область видимості заплутана.
  • Об'єкт (object): Об’єкт - це особливий вид значення в JavaScript. Найцікавіше в об’єктах полягає в тому, що вони можуть мати зв’язок з іншими значеннями. Наприклад, об’єкт {flavor: "vanilla"} має властивість flavor, яке вказує на значення "vanilla". Думайте про об’єкт як про «своє» значення із «дротами» від нього.

    • Властивість (property): Властивість схожа на «дріт», що стирчить з предмета і вказує на якесь значення. Вона може нагадувати вам змінну: вона має назву (наприклад, flavor) і вказує на значення (наприклад, „ваніль“). Але на відміну від змінної, властивість "живе" в самому об’єкті, а не в якомусь місці вашого коду (області видимості). Властивість вважається частиною об’єкта, але значення, на яке воно вказує, таким не є.
    • Об’єктний літерал (object literal): об’єктний літерал - це спосіб створити значення об’єкта, буквально записавши його у свою програму, наприклад {} або {flavor: "vanilla"}. Усередині {} ми можемо мати кілька властивостей: пари значень, розділені комами. Це дозволяє нам встановити, де властивості “дроти” вказують на наш об’єкт.
    • Ідентичність об’єкта (object identity): Раніше ми вже згадували, що 2 дорівнює 2 (іншими словами, 2 === 2), тому що, коли ми пишемо 2, ми “викликаємо” одне і те ж значення. Але щоразу, коли ми пишемо {}, ми завжди отримуватимемо інше значення! Отже, {} не дорівнює іншому {}. Спробуйте це в консолі: {} === {} (результат false). Коли комп’ютер зустрічає 2 у нашому коді, він завжди дає нам одне і те ж значення 2. Однак об’єктні літерали бувають різними: коли комп’ютер зустрічає {}, він створює новий об’єкт, який завжди є новим значенням. Отже, що таке ідентичність об’єкта? Це ще один термін рівності або однаковості значень. Коли ми говоримо “a і b ідентичні”, ми маємо на увазі “a і b вказують на одне і те ж значення” (a === b). Коли ми говоримо “a і b не ідентичні”, ми маємо на увазі “a і b вказують на різні значення” (a !== b).
    • Запис з крапкою (dot notation): Коли ви хочете прочитати властивість з об’єкта або призначити її, ви можете використовувати запис з крапкою (.). Наприклад, якщо змінна iceCream вказує на об’єкт, властивість якого flavor вказує на "chocolate", написання iceCream.flavor дасть вам "chocolate".
    • Запис в квадратних дужках (bracket notation): Іноді ви не знаєте назви властивості, яку хочете прочитати заздалегідь. Наприклад, можливо, іноді ви хочете прочитати iceCream.flavor, а іноді ви хочете прочитати iceCream.taste. Запис у квадратних дужках ([]) дозволяє читати властивість, коли її назва сама є змінною. Наприклад, скажімо, що let ourProperty = 'flavor'. Тоді iceCream[ourProperty ] дасть нам "chocolate". Цікаво, що ми можемо використовувати його і при створенні об’єктів: { [ourProperty]: "vanilla" }.
    • Мутація (mutation): Ми говоримо, що об’єкт мутується, коли хтось змінює його властивість, щоб вказати на інше значення. Наприклад, якщо ми оголосимо let iceCream = {flavor: "vanilla"}, ми можемо згодом змінити його за допомогою iceCream.flavor = "chocolate". Зауважте, що навіть якщо ми використовували const для декларування морозива, ми все одно могли б мутувати iceCream.flavor. Це пов’язано з тим, що const запобігає лише призначенню самої змінної iceCream, але ми змінили властивість (flavor) об’єкта, на який вона вказувала. Деякі люди взагалі заріклися від використання const, оскільки вважають це занадто оманливим.

    Коментар від programming mentor - схоже я з тих людей, яких Ден називає педантичними у використанні const. Насправді нічого в тому складного немає, достатньо лише пам’ятати про те що const дає захист від переприсвоювання і нічого більше. Раджу дотримуватися простого правила - завжди використовуйте const, лише коли стає зрозуміло, щоб без мутації змінної не можна обійтися - тоді змінюйте його на let, а var не використовуйте взагалі.

    Подивіться відео від мене про те як можна уникати мутацій:

  • Масив (array): масив - це об’єкт, який представляє список речей. Коли ви пишете літерал масиву, як ["banana", "chocolate", "vanilla"], ви по суті створюєте об’єкт, властивість якого називається 0 вказує на значення рядка “banana”, властивість називається 1 вказує на значення "chocolate", і властивість 2 до значення "vanilla". Було б незручно писати {0: ..., 1: ..., 2: ...}, тому масиви корисні. Є також деякі вбудовані способи роботи з масивами, зокрема .map(), .filter() та .reduce(). Не впадайте у відчай, якщо .reduce() здається заплутаним - всі через це проходили.
  • Прототип (prototype): Що станеться, якщо ми прочитаємо властивість, яка не існує? Наприклад, iceCream.taste (але наша властивість називається flavor). Проста відповідь - ми отримаємо спеціальне значення undefined. Більш точна відповідь полягає в тому, що більшість об’єктів у JavaScript мають "прототип". Ви можете уявити прототип як “приховану” властивість кожного об’єкта, який визначає “куди шукати далі”. Отже, якщо в iceCream немає властивості flavor, JavaScript шукатиме властивість смаку на своєму прототипі, потім на прототипі цього об’єкта і т.д., і дасть нам undefined лише в тому випадку, якщо він дійде до кінця цього «ланцюжка прототипів», не знайшовши flavor. Ви рідко будете взаємодіяти з цим механізмом безпосередньо, але це пояснює, чому наш об’єкт iceCream має метод .toString(), який ми ніколи не визначали - він походить від прототипу.
  • Функція (function): Функція - це особливе значення з однією ціллю: вона представляє деякий код у вашій програмі. Функції зручні, якщо ви не хочете писати один і той же код багато разів. “Виклик” такої функції, як sayHi(), повідомляє комп’ютеру запустити код всередині неї, а потім повернутися туди, де він був у програмі. Існує багато способів визначити функцію в JavaScript, з невеликими відмінностями в тому, що вони роблять.

    Коментар від programming mentor: новачкам буває непросто зрозуміти концепцію функції. Якщо зі змінними все досить просто - це якась коробочка, контейнер, куди ми щось можемо покласти і звідти взяти. То функцію я пояснюю як коробочку де живе звірятко, яке робить нам дещо корисне, ми даємо йому щось на вхід, наприклад шоколадку і фольгу, а звірятко загортає нам шоколадку в фольгу та повертає результат на вихід. Також замість коробочки можна уявити кімнату де живе те звірятко - тут все те саме, але зрозуміліше стає поняття виклику функції - ми таким чином заходимо в кімнату, чекаємо поки звірятко заверне шоколадку в фольгу, а потім виходимо і повертаємося в те місце, де були раніше.

    • Аргументи (arguments) або параметри (parameters): Аргументи дозволяють передавати деяку інформацію вашій функції з того місця, де ви її викликаєте: sayHi("Амелі"). Усередині функції вони діють подібно до змінних. Вони називаються "аргументами" або "параметрами" залежно від того, де ви їх читаєте (визначення функції або виклик функції). Однак ця різниця в термінології є педантичною, і на практиці ці два терміни використовуються як взаємозамінні.
    • Функціональний вираз (function expression): Раніше ми встановлювали для змінної значення рядкового типу, наприклад let message = "I am the walrus". Виявляється, ми також можемо встановити змінну для функції, наприклад, let sayHi = function () {}. Частина після = тут називається функціональним виразом. Це дає нам особливе значення (функцію), що представляє наш фрагмент коду, тому ми можемо викликати його пізніше, якщо хочемо.
    • Декларація функції (function declaration): Не дуже зручно щоразу писати щось на зразок let sayHi = function () {}, тому замість цього ми можемо використовувати коротшу форму: function sayHi () {}. Це називається оголошенням (декларацією) функції. Замість того, щоб вказати ім’я змінної зліва, ми ставимо його після ключового слова function. Ці два стилі переважно взаємозамінні.
    • Підняття функцій (function hoisting): Зазвичай ви можете використовувати змінну лише після її оголошення, якщо використати let або const. Це не дуже зручно з функціями, оскільки часто вони викликати одна одну, і важко відстежити, яка функція використовується іншими, а тому її потрібно визначити спочатку. Для зручності, коли (і лише тоді!) Ви використовуєте синтаксис оголошення функції, порядок їх визначень не має значення, оскільки вони отримують “підняття”. Це оригінальний спосіб сказати, що концептуально всі вони автоматично переміщуються у верхню частину області видимості. В той момент, коли вони потрібні для виклику, вони вже визначені.
    • this. Напевно, найбільш неправильно зрозуміла концепція JavaScript, this - це як особливий аргумент функції. Ви самі не передаєте this функції. Натомість сам JavaScript передає this, залежно від того, як викликається функція. Наприклад, виклики з використанням крапки (запис з крапкою) - iceCream.eat() - отримає спеціальне значення this з того, що було до крапки (у нашому прикладі iceCream). Значення this всередині функції залежить від того, як функція викликається, а не де вона визначена. Помічники, такі як .bind(), .call() та .apply(), дозволяють вам мати більший контроль над значенням this.
    • Стрілочні функції (arrow functions). Стрілочні функції схожі на вирази функцій. Ви оголошуєте їх так: let sayHi = () => {}. Вони стислі та часто використовуються для запису в один рядок. Стрілочні функції є більш обмеженими, ніж звичайні функції - наприклад, вони взагалі не мають this. Коли ви пишете this всередині стрілочної функції, вона використовує this найближчої “звичайної” функції вище. Це схоже на те, що сталося б, якби ви використовували аргумент або змінну, яка існує лише у функції вище. Практично це означає, що люди використовують стрілочні функції, коли хочуть “побачити” те саме всередині них, що і в коді, що їх оточує.
    • Прив’язка функції (function binding): Зазвичай прив’язка функції f до певного значення this та аргументів означає створення нової функції, яка викликає f із цими заздалегідь визначеними значеннями. У JavaScript є вбудований помічник, який називається .bind(), але ви також можете зробити це вручну. Прив’язка була популярним способом змусити вкладені функції “бачити” те саме значення this, що і зовнішні функції. Але зараз цей варіант використання обробляється стрілочними функціями, тому прив’язка використовується не так часто.

    Відео від programming mentor про особливості і як можна поліпшити .bind():

  • Стек викликів (call stack): Виклик функції - це все одно, що зайти в кімнату. Кожного разу, коли ми викликаємо функцію, змінні всередині неї спочатку ініціалізуються. Отже, кожен виклик функції схожий на побудову нової “кімнати” з її кодом та введення її. Змінні нашої функції “живуть” у цій кімнаті. Коли ми повертаємось із функції, ця “кімната” зникає з усіма її змінними. Ви можете візуалізувати ці кімнати як вертикальний будинок з однакових кімнат - стек викликів. Коли ми виходимо з функції, ми повертаємось до функції “нижче” її у стеку викликів.
  • Рекурсія (recursion): Рекурсія означає, що функція викликає саму себе зсередини. Це корисно, коли ви хочете повторити те, що ви щойно зробили у своїй функції, але для різних аргументів. Наприклад, якщо ви пишете пошукову систему, яка сканує Інтернет, функція collectLinks(url) може спочатку збирати посилання зі сторінки, а потім викликати себе для кожного посилання, поки воно не відвідає всі сторінки. Ловушка рекурсії полягає в тому, що легко писати код, який ніколи не закінчується, оскільки функція продовжує викликати себе завжди. Якщо це станеться, JavaScript зупинить виконання з помилкою під назвою “переповнення стека”. Ця помилка називається таким чином тому що у нас є занадто багато викликів функцій, складених у нашому стеку викликів, в ньому закінчилося місце, він у прямому смислі переповнився.
  • Функція вищого порядку (higher-order function): Функція вищого порядку - це функція, яка має справу з іншими функціями, приймаючи їх як аргументи або повертаючи. Спочатку це може здатися дивним, але слід пам’ятати, що функції - це значення, і ми можемо їх передавати точно також як це робимо з числами, рядками чи об’єктами. Цим підходом можна зловживати, але використовуючи його в міру ми отримуємо дуже виразний код.
  • Зворотний виклик (callback). Зворотній виклик насправді не є терміном JavaScript. Це скоріше шаблон. Це коли ви передаєте функцію як аргумент іншій функції, очікуючи, що вона знову викличе вашу функцію. Ви очікуєте “зворотного виклику”. Наприклад, setTimeout() приймає функцію зворотного виклику і… повертаються до вас після закінчення часу. Але в функціях зворотного виклику немає нічого особливого. Вони є звичайними функціями, і коли ми говоримо “зворотний виклик”, ми говоримо лише про свої очікування.
  • Замикання (closure): Зазвичай, коли ви виходите з функції, всі її змінні “зникають”. Це тому, що вони вже нікому не потрібні. Але що трапиться, якщо ви оголосите функцію всередині функції? Тоді внутрішню функцію можна буде викликати пізніше і звертатися до змінних зовнішньої функції. На практиці це дуже корисно! Але для того, щоб це працювало, змінні зовнішньої функції повинні десь “закріпитися”. Отже, у цьому випадку JavaScript дбає про те, щоб “підтримувати змінні живими”, а не “забувати” їх, як це зазвичай робиться. Це називається “замиканням”. Хоча замикання часто вважають неправильно зрозумілим аспектом JavaScript, ви, ймовірно, використовуєте їх регулярно, навіть не усвідомлюючи цього!

    Відео про замикання від мене programming mentor:

Деякі важливі речі Ден опустив, то я їх додаю з поясненнями від себе (programming mentor).

  • Ідентифікатор (identifier) - це ім’я, яке програміст дає певним сутностям в програмі, зокрема змінним та функціям. Для комп’ютера те, що ви записуєте в ідентифікатори, не має значення, але для програміста вони особливо важливі, причому настільки, що часто сам код є другорядним по відношенню до імен, бо вдало вибране ім’я дозволяє легко зрозуміти що робить код, не вникаючи в деталі, і навпаки - невдало обране ім’я може призвести до катастрофічних наслідків, коли розробник невірно розуміє що має робити код. Важливо не лише наповнювати ідентифікатори змістом, а ще й використовувати коректні частини мови та й використовувати коректні форми слів. Наприклад, функції мають щось робити, тому для них використовуємо зазвичай дієслова, наприклад calculate(), а змінні - зберігають значення, тому це має бути іменник, наприклад, client, але якщо в нас масив значень, тоді вже варто написати clients, що означає множину.
  • Виключна ситуація, виняток (exception) - це механізм, за допомогою якого більшість сучасних мов програмування, і JS в тому числі, реагують на помилки під час виконання коду. Виглядає це таким чином - для деяких помилок виконання коду припиняється і йде до обробника catch, який може обробити помилку та відновити хід виконання. Але діє це лише тоді, коли код був огорнутий в try...catch, інакше виконання всієї програми зупиниться і ми побачимо повідомлення в консолі. Окремо слід зазначити, що на відміну від більшості інших мов програмування, викликати виняток у JS зовсім не так просто як здається - тут і на нуль можна поділити, і додати будь що до будь чого, і навіть звертатися за межі допустимих індексів масиву. То треба бути уважним і не покладатися на винятки в тих ситуаціях, у яких вони зазвичай використовуються в інших мовах програмування.
  • Подія (event). Хоча події не є частиною самої мови, а самого середовища, в якому вона виконується, вони використовуються дуже активно, тому їх потрібно знати. Подія - це такий собі зв’язок між мовою та зовнішнім середовищем, точка входу, у якій ми можемо приєднати певну функцію до чогось, що приходить із зовнішнього середовища, наприклад, натискання користувачем кнопки і тому подібне.
  • Конкурентність (concurrency) чи асинхронність (asynchronous) - це механізм, за допомогою якого JavaScript імітує паралельну роботу. На відміну від мов програмування, які підтримують багатопоточність, тобто по-справжньому дозволяють виконувати код паралельно, у JavaScript такої можливості немає, але це не обов’язково погано, бо з багатьма задачами механізм асинхронності JavaScript справляється дуже добре, і на відміну від роботи з потоками, він значно простіший для розробника.
  • Цикл подій (event loop) - це механізм, який реалізовує асинхронність, тобто імітацію паралельної роботи. Працює він таким чином, що в потрібний момент розміщує операції, що очікували виконання, в стек викликів в той момент, коли він пустий. Тобто, якщо ми напишемо setTimeout( ()=> console.log("hello"), 1000 ), то "hello" в консольці з’явиться не відразу, а через одну секунду. Але якщо говорити точніше, то не раніше, ніж за одну секунду, бо досягається це за рахунок того, що середовище виконання JavaScript спочатку запам’ятовує що треба виконати і коли саме, а потім цикл подій “закидує” функцію в стек викликів, але за умови, що він пустий - якщо там виконується якийсь код, то доведеться чекати його виконання.
  • Проміс (promise). Проміс можна перекласти як “обіцянка” і це спрощує розуміння, однак так ніхто не говорить, і це слово використовується без перекладу. Це механізм, який дозволяє спростити написання асинхронного коду, за рахунок того, що ми не передаємо асинхронній функції у якості аргументів функції зворотного виклику, а отримаємо “обіцянку” виконати наш код, коли завершиться асинхронна операція. Потім, коли обіцянка здійснюється, наш код виконується.
  • Async/await. Це ключові слова, що є по суті синтаксичним цукром - тобто такою конструкцією в коді, що спрощує його написання і ґрунтується на інших концепціях. Вони основані на промісах і дозволяють написати функцію з ключовим словом async на початку, що в свою чергу дасть можливість використати в тілі функції ключове слово await. Це дозволяє писати асинхронний код таким чином, що виглядає він як звичайний, але в тому місці де написано await він насправді не зупиняється і не блокує виконання JavaScript.
  • Функція-конструктор (constructor function). Функція-конструктор - це досить незвична для розробників з інших мов концепція, вона була додана в JavaScript для того щоб була можливість конструювати об’єкти як на конвеєрі ще до того як в мові з’явилися класи. Відрізнити функцію-конструктор дуже просто - її ім’я починається з великої літери, але ця вимога не є вбудованою в мову, це виключно домовленість. Функції-конструктори мають бути використані лише з ключовим словом new, наприклад, таким чином: const client = new Client(name), у такому випадку всередині функції ми можемо використати this щоб зберегти name: this.name = name. Робити return this не обов’язково, JS піклується про те щоб повернути коректне значення. Важливий нюанс - методи варто задавати не в самій функції, а за її межами у властивості prototype, яка існує саме для цього. Відповідно виглядати наш код, який створює клієнта з методом, що друкує його ім’я в консоль браузера буде наступним чином:
function Client(name) {
  this.name = name;
} 
Client.prototype = function() {
  console.log(this.name);
}
  • Клас (class). В JS є ключове слово class, що теж є синтаксичним цукром і дозволяє у звичний для програмістів, що прийшли з інших мов програмування декларувати можливість створення об’єктів. В інших мовах класи є поняттям самої мови і по суті являються умовним кресленням, з якого створюються об’єкти. Однак у випадку JavaScript - це лише синтаксичний цукор, і під час виконання коду такого поняття як клас не існує, все конвертується у функції-конструктори, ланцюжки прототипів та об’єкти. Хоча багато хто з олдскульних джаваскриптерів класи недолюблює, я вважаю їх корисними, вони дозволяють робити більш виразний код.

P.S. Якщо бажаєте вивчити сучасний JavaScript, і не просто в теорії, а на практиці нарешті зрозуміти і почати застосовувати всі ті наведені вище концепції та й багато більше, то запрошую на унікальний курс ScriptJedi42, який я проводжу кілька разів на рік з невеликими групами людей. За шість тижнів теорії і практики з моєю підтримкою і особистим рев’ю кожного рядку вашого коду ви раз і назавжди закриєте для себе це питання незалежно від того, чи вчитеся з нуля, чи вже почали вивчати мову якийсь час, але відчуваєте що не до кінця її розумієте і довго топчетеся на місці. Якщо реєструватися заздалегідь, то відкривається доступ до всіх завдань і матеріалів, можна готуватися у власному темпі до початку навчання групи.