Довідник з фундаментальної теорії JavaScript. Вивчаємо програмування українською

Усі статті, обговорення, новини про Front-end — в одному місці. Підписуйтеся на телеграм-канал!

Усім привіт! Мене звати Маша Образцова, я Front-End Developer в продуктовій IT-компанії Universe. Зараз багато людей вивчають програмування з нуля — за книгами, статтями та відеоуроками. Але якщо вони не знають англійської на достатньому рівні, доводиться користуватися російськими джерелами, альтернативи яким не так багато. Аби виправити це неподобство, я вирішила зібрати в один текст стислу фундаментальну теорію JavaScript українською мовою.

Тут зібрані базові поняття, синтаксис, принципи роботи з числами та рядками, функціями, операторами, масивами тощо. Цей довідник буде корисним усім, хто робить перші кроки у програмуванні або заґуґлив складнішу тему, але не до кінця зрозумів логіку коду і мусить освіжити знання.

Наприкінці статті на вас чекає добірка задач з Javascript для новачків.

Базові поняття

Для початку ознайомимось з основними поняттями будь-якої мови програмування. Якщо ви вже колись стикалися з написанням програм (перша пара в університеті, половину якої ви проспали, теж рахується), то можете відразу переходити до розділу з типами даних у JavaScript ;)

Комп’ютерна програма — це текстовий файл, у якому за допомогою набору правил (іншими словами, мови програмування) записані послідовності команд. Комп’ютер визначає мову, якою була написана програма, використовуючи розширення файлу (наприклад, для JavaScript є розширення.js), і рядок за рядком виконує команди в тій послідовності, у якій вони були записані.

Команда — рядок коду, який каже комп’ютеру виконати певну дію. Однією з найпростіших команд є console.log() — вона використовується для виведення повідомлень у консоль комп’ютера або браузера.

Щоби побачити роботу цієї команди в дії, відкрийте браузер, натисніть f12 або Command + option + I та оберіть вкладку Console. Якщо ви користуєтеся Safari, то шукайте консоль через вкладку «Розробка» (якщо вона відсутня в меню, інструкція, як її знайти, за посиланням).

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

Тепер наберіть console.log(’Hello, world!’); та натисніть Enter.

Вітаю, ви щойно написали свою першу команду!

Скрин екрана в браузері Chrome

Скрин екрана в браузері Safari

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

Дані, такі як числа або рядки, записуються за допомогою відповідних літералів: числових (1, −5, 9.5) або рядкових (‘Hello, world!’). Рядкові літерали завжди записуються в лапках, інакше комп’ютер буде сприймати їх як змінні.

Змінні, як і у звичайній математиці, — це щось на кшталт «коробок», у яких зберігаються дані. Згадайте, як колись шукали Х у рівняннях. От цей Х — і є змінна. У JavaScript змінні оголошуються за допомогою двох кодових слів — let (якщо може змінити своє значення під час виконання програми) та const (коли значення присвоюється один раз при оголошенні).

let a = 5;
const b = 6;

console.log(a); // результат: 5
console.log(b); // результат: 6

a = 7;
console.log(a); // результат: 7

b = 8;
console.log(b); //результат: помилка, адже не можна перезаписувати змінні, оголошені через const

Важливо пам’ятати, що в гарно написаному коді з назви змінної зрозуміло, які саме дані в ній зберігаються. Це полегшує сприйняття вашого коду іншими програмістами з команди. Також, якщо назва змінної складається з декількох слів, заведено писати кожне наступне слово з великої літери.

Приклади гарних назв змінних: price, cancelledPrice

Приклади поганих назв: a (не зрозуміло, які дані зберігаються в змінній), cancelledprice (обидва слова зливаються в одне, назва стає нечитабельною)

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

Коментар в один рядок позначають за допомогою //

//обчислюємо розмір знижки у відсотках
let discount = (price / cancelledPrice) * 100;

Багаторядкові коментарі позначають за допомогою символів /**/

/* це початковий рядок коментаря,

який пояснює, що наступна ділянка коду обчислює

розмір знижки у відсотках */

let discount = (price / cancelledPrice) * 100;

Ще одне поняття математики, яке перейшло в програмування, — це вирази. Це певні операції над змінними. Результати обчислення виразів також можна записувати в змінні.

Для прикладу обчислимо розмір знижки на товар у відсотках:

let price = 5;
let cancelledPrice = 10;
let discount = (price / cancelledPrice) * 100;
console.log(discount); // результат: 50

При виконанні цього коду комп’ютер спочатку підставив замість змінних їхні значення (5 і 10 відповідно), обчислив отриманий вираз та записав його результат у змінну discount.

Типи даних

Мови програмування можна умовно поділити на два типи: зі статичною типізацією даних та з динамічною типізацією. За статичної типізації розробник при оголошенні змінної відразу оголошує і її тип (наприклад, кодове слово string у мові С++ означає, що в цю змінну можна записати лише рядок). Динамічні мови програмування під час виконання коду самостійно визначають значення якого типу покладено в змінну.

JavaScript належить до мов із динамічною типізацією. У попередньому розділі вже згадувалось, що змінну оголошують за допомогою двох ключових слів (let та const), які нічого не кажуть про тип даних цієї змінної.

Динамічну типізацію називають також слабкою, адже в одну й ту саму змінну ми можемо перезаписувати дані різних типів.

let a = 5;

a = ‘amazing string’;

a = true;

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

Щоби зменшити вірогідність таких помилок, існує режим ‘use strict’ (оголошується у файлі з кодом програми). На відміну від звичайного режиму, який просто пропускає потенційні помилки та виконує програму далі, strict-mode розцінює їх, як реальні помилки та зупиняє роботу програми, таким чином попереджаючи розробника про необхідність змінити логіку коду.

"use strict"
randomVariable = 17;
// режим "use strict" викине помилку, адже змінна не була оголошена
// через ключові слова let або const
// (в старих версіях JavaScript оголошення без ключових слів було дозволено)

let public = 'some string'
// викине помилку, адже public — це зарезервоване ключове слово, його не можна
// використовувати як змінну

Більше про обмеження, які накладає use strict в документації MDN.

У JavaScript існує 8 типів даних. BigInt (числові значення великого порядку) та Symbol (унікальні кодові слова-символи) використовуються достатньо рідко. Натомість наступні шість типів є дуже розповсюдженими:

  1. Number — числове значення (1, 245.1, −89).
  2. String — рядкові значення, записуються лише в лапках (‘Hello, world!’, ‘Awesome string’).
  3. Boolean — логічне значення типу правда/неправда, має лише два значення: true або false.
  4. Undefined — невизначене значення. Змінні, яким ще не присвоювали жодного значення, мають значення undefined.
  5. Null — «порожнє» значення. Присвоюють змінним, у яких згодом буде записане значення, але наразі воно відсутнє.
  6. Object — простіше кажучи, це набір ключ-значення (наприклад, {name: ‘John’; age: 25;})
  7. Array (масив) — набір даних одного типу (наприклад, список продуктів був би масивом рядків).

Перші чотири типи даних є примітивними — їхнє значення не можна частково змінити, тільки повністю переписати значення. Наприклад, коли ви хочете внести зміни у вже існуючий рядок, з погляду JavaScript ви створюєте інший новий рядок.

Натомість коли ви додаєте новий ключ в об’єкт, ви просто вносите зміни в існуючий об’єкт, а не створюєте новий.

Аби дізнатися тип змінної або навіть виразу, існує оператор ’typeof()’, який повертає рядок із типом даних.

const age = 23;

console.log(typeof(age)); // 'number'

Зверніть увагу на особливість JavaScript: попри те, що null виноситься як окремий вид даних, оператор typeof поверне вам значення ‘object’.

Робота з числами

У JavaScript операції із числами аналогічні операціям з математики: складання (+), віднімання (-), множення (*), ділення (/), піднесення до степеня (**) та взяття остачі від ділення (%).

console.log(12 + 6); // 18

console.log(18 - 8); // 10

console.log(6 * 6); // 36

console.log(12 / 6); // 2

console.log(2 ** 3); // 8 = 2 * 2 *2

console.log(8 % 3); // 2 = 8 — 3 — 3

Операція взяття остачі від ділення часто використовується при визначенні, чи є число парним.

Варто також пам’ятати про пріоритет операцій: обчислення виконуються по черзі зліва направо, але множення та ділення мають вищий пріоритет, тому виконуються першими. Як у звичайній математиці, JavaScript використовує дужки (), щоби змінювати пріоритет операцій:

console.log(6 + 2 * 3); // 12
console.log((6 + 2) * 3); // 24

Корисні методи для роботи з числами

1. toString(base) — повертає рядкове значення числа в заданій системі числення base (за замовчуванням береться десяткова). Тобто метод переводить число в задану систему числення та повертає результат у вигляді рядка:

let num = 255;
num.toString(16);  //ff — репрезентація числа 255 в шістнадцятковій системі
num.toString(2);   //11111111 — репрезентація числа 255 у двійковій системі

2. Math.floor() — округлює число в менший бік (3.6 стає 3, −1.1 стає −2).

3. Math.ceil() — округлює число в більший бік (3.6 стає 4, −1.1 стає −1).

4. Math.round() — округлює до найближчого числа, як звичайне округлення в математиці (3.4 стає 3, 3.5 стає 4).

5. toFixed(n) — округлює до n-цифр після коми та повертає результат у вигляді рядка

let num = 25.554;
num.toFixed(1);  // ‘25.6’
num.toString(2);   // ‘25.55’

Робота з рядками

Оголошувати рядкові дані у JavaScript можна за допомогою одинарних (), подвійних (") та зворотних (`) лапок.

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

Зворотні лапки використовуються для рядків, які містять у собі якісь обчислення, змінні (записуються за допомогою ${}) або перенесення рядка. Такий спосіб оголошення рядка називається інтерполяцією.

let name = ‘Sherlock’;

let message = `Hello, ${name}!`;

Об’єднання рядків за допомогою знака ‘+’ називається конкатенацією. Її можна використовувати як альтернативу інтерполяції, щоб вставити змінну в рядок, проте інтерполяція використовується значно частіше.

let name = ‘Sherlock’;

let message = ‘Hello, ’ + name + ‘!’; // ‘Hello, Sherlock!’

let string1 = ‘123’ + 45; // ‘12345’

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

Для визначення довжини рядка є властивість length:

let name = ‘Sherlock’;

console.log(name.length); // 8

Отримати доступ до символу рядка можна за допомогою його індексу (індексація символів починається з 0, а останній індекс дорівнює довжині рядка мінус 1):

let name = ‘Sherlock’;
console.log(name[0]); // S
console.log(name[name.length - 1]); // k

console.log(name[-2]); // undefined

Спроба отримати символ із негативним індексом завжди повертає undefined.

У JavaScript рядки — це імутабельні об’єкти, тобто внести зміни у вже існуюче значення неможливо. Єдиний шлях змінити значення рядкової змінної — привласнити їй повністю нове значення:

let name = ‘Hi’;

name[0] = ‘h’ // error, doesn’t work

name = ‘h’ + name[1];

console.log(name); // hi

Корисні методи для роботи з рядками

1. toUpperCase() — змінює регістр рядка або символу на верхній.

2. toLowerCase() — змінює регістр рядка або символу на нижній.

console.log( 'String'.toUpperCase() ); // STRING
console.log( 'String'.toLowerCase() ); // string

3. includes(substr) — повертає true, якщо рядок містить підрядок substr.

4. slice(indexStart, indexEnd) — повертає підрядок, вирізаний з основного рядка, починаючи із символу з індексом indexStart, та закінчуючи символом indexEnd (але сам кінцевий символ не включається). indexEnd — опціональний параметр, якщо він не вказаний, кінцем вважається кінець основного рядка. Початковий індекс також може бути від’ємним, у такому випадку нумерація йде від кінця рядка до його початку. Якщо початковий індекс більший за довжину рядка або кінцевий індекс, повертається порожній рядок.

let str1 = "The morning is upon us."; // str1.length = 23.
console.log(str1.slice(1, 8)); // he morn
console.log(str1.slice(4, -2)); // morning is upon u
console.log(str.slice(-3)); // us.
console.log(str.slice(-3, -1)); // us
console.log(str1.slice(12)); // is upon us.
console.log(str1.slice(30)); // ""

5. substring(indexStart, indexEnd) — повертає рядок, вирізаний з основного рядка, починаючи з символу з індексом indexStart, та закінчуючи символом indexEnd (але сам кінцевий символ не включається). Різниця з методом slice() у тому, що цей метод прирівнює всі негативні значення до 0.

const str = 'Mozilla';
console.log(str.substring(-3, 3)); // Moz
console.log(str.substring(2)); // zilla

6. localeCompare() — порівнює два рядки з урахуванням локалі й регістру. Порівняння відбувається посимвольно, символи порівнюються за їхнім кодуванням. Повертає 0, якщо рядки повністю ідентичні. Позитивне число, якщо перший рядок стоїть за алфавітом після другого й негативне, якщо навпаки.

"a".localeCompare("c"); // негативне значення, ‘a’ за алфавітом раніше ‘c’
"с".localeCompare("а"); // негативне значення, ‘с’ пізніше ‘а’
"a".localeCompare("а"); // 0, рядки ідентичні

Більше методів для роботи з рядками можна знайти в документації MDN (вкладка methods).

Функції

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

Для вирішення цієї проблеми є функції. Це фрагмент коду, який можна викликати багато разів у програмі, уникаючи при цьому дублювання коду.

Для оголошення функції (function declaration) використовують ключове слово function:

function printHello() {
	console.log(‘Hello!’)
}

У прикладі printHello — це ім’я функції, а те, що знаходиться у фігурних дужках {} — тіло функції. Ця функція не має параметрів — значень, які передаються у функцію в круглих дужках () та використовуються у її тілі.

function printHelloWithName(name) {
	console.log(`Hello, ${name}!`)
}

Якщо функція має декілька параметрів (наприклад, name та surname), то вони пишуться через кому:

function printHelloWithName(name, surname) {
	console.log(`Hello, ${name} ${surname}!`)
}

Функції з прикладів дуже прості та лише виводять отримані параметри на екран. Але вони також можуть відтворювати складну логіку і, що важливо, можуть повертати результат за допомогою ключового слова return:

// оголошення функції
function calculateSum(a, b) {
	return a + b
}

// виклик функції
calculateSum(3, 2) // повертає 5

Слово return перериває виконання функції, повертаючи конкретне значення. Тобто код, написаний після нього, не буде виконаний.

Такі функції, як calculateSum з прикладу вище, називають чистими (pure function) — при однакових вхідних даних (наприклад, 3 і 2) вона завжди повертатиме однаковий результат (5). А з назви функції відразу зрозуміло дію, яку вона виконує, і що повертає.

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

Якщо ж ми всередині функції будемо, наприклад, виводити параметри в консоль, то вона матиме сторонні ефекти (side effects), які неочевидні з назви — тож ця функція перестане бути чистою (impure function).

// приклад чистої функції (pure function)
function calculateSum(a, b) {
	return a + b
}

// приклад функції зі сторонніми ефектами (impure function)
function calculateMultiplication(a, b) {
	console.log(a, b)
	return a * b
}

Зверніть увагу, якщо функція оголошена за допомогою ключового слова function, її можна викликати в будь-якому місці програми (навіть до оголошення):

// виклик функції
calculateSum(3, 2) // повертає 5

// оголошення функції традиційним способом
function calculateSum(a, b) {
	return a + b
}

Існує також альтернативний спосіб оголошення функції шляхом її запису в змінну:

// такий синтаксис називають функції-стрілки (arrow functions)
const calculateSumArrow = (a, b) => {
	return a + b
}

// функції-стрілки можна викликати ТІЛЬКИ після їхнього оголошення
calculateSumArrow(3, 2) // повертає 5

У будь-якій мові програмування існує поняття «область видимості змінних» (variables scope). Простими словами — це область, у якій ця змінна доступна (видима) і ми можемо нею користуватися.

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

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

Умовні оператори

Часто трапляється, що певну частину коду потрібно виконати лише за певних умов. У цих випадках використовується конструкція з умовним оператором if.

Припустимо, нам потрібно виводити ‘Adult’, якщо вік людини більше ніж 18:

// умова вказується в дужках після кодового слова if
if (age >= 18) {
	// код, що має бути виконаний, якщо виконується умова вище
	console.log('Adult')
}

У ситуації, коли залежно від виконання умови треба виконати дві різні дії, використовується конструкція if... else:

if (age >= 18) {
	console.log('Adult')
} else {
	// код, що має бути виконаний лише якщо НЕ виконується умова вище
	console.log('Child')
}

Існує також оператор else if, якщо таких умов більше, ніж 2:

if (age >= 18) {
	console.log('Adult')
} else if (age >= 12) {
	// код має бути виконаний лише якщо:
	// НЕ виконується умова 1 (age >= 18)
	// виконується умова 2 (age >= 12)
	console.log('Teenager')
} else {
	// код виконається, якщо не виконується жодна з умов вище
	console.log('Child')
}

Умовні оператори if можна вкладати одне в одного, якщо нам необхідно перевірити декілька умов:

if (isHungry) {
	if (hasEnoughMoney) {
		buyFood()
	}
} else {
	console.log('Go home')
}

У прикладі вище ми купуємо їжу тільки тоді, коли ми голодні та маємо достатньо грошей.

Але читати такий вкладений код не дуже зручно, тому існує спосіб поєднувати умови в одну за допомогою логічних операторів.

Усього їх існує три:

  1. || (OR) — повертає true (тобто вважає умову виконаною), якщо хоча б одна з умов правдива
  2. Важливо, що оператор || повертає true, як тільки знаходить ПЕРШУ правдиву умову. Тобто оператор дійде до умови 2 тільки в тому випадку, якщо не виконується умова 1. Якщо обидві умови повертають false, оператор також повертає false
  3. && (AND) — повертає true, якщо виконуються ВСІ умови.
  4. Якщо умова 1 чи умова 2 повернуть false, оператор AND також поверне false, тобто умова не виконується.
  5. ! (NOT) — змінює значення на протилежне (true на false і навпаки).
  6. При використанні цього оператора та if важливо розуміти, що код всередині if виконується, якщо умова в дужках поверне true. У випадку використання оператора ! умова 1 повинна не виконуватись (повертати false), тому що not-false === true.
let isHungry = true
let hasEnoughMoney = true

// змінна типу boolean, яка міститиме результат виконання логічних операторів
let result

// true
result = isHungry && hasEnoughMoney
// true
result = isHungry || hasEnoughMoney
// false, оскільки значення в дужках дорівнює true
result = !(isHungry && hasEnoughMoney)

isHungry = true
hasEnoughMoney = false

// false, оскільки одне зі значень дорівнює false
result = isHungry && hasEnoughMoney
// true, оскільки одне зі значень дорівнює true
result = isHungry || hasEnoughMoney
// true, оскільки значення в дужках дорівнює false
result = !(isHungry && hasEnoughMoney)

isHungry = false
hasEnoughMoney = true

// false, оскільки одне зі значень дорівнює false
result = isHungry && hasEnoughMoney
// true, оскільки одне зі значень дорівнює true
result = isHungry || hasEnoughMoney
// true, оскільки значення в дужках дорівнює false
result = !(isHungry && hasEnoughMoney)

isHungry = true
hasEnoughMoney = true

// false, оскільки обидва значення дорівнюють false
result = isHungry && hasEnoughMoney
// false, оскільки обидва значення дорівнюють false
result = isHungry || hasEnoughMoney
// true, оскільки значення в дужках дорівнює false
result = !(isHungry && hasEnoughMoney)

Якщо нам потрібно повертати значення з умовного оператора та записувати його в змінну, то у традиційного запису if... else є альтернатива — тернарний оператор:

змінна = умова ? значення, якщо умова виконується : значення, якщо не виконується

let title

// запис за допомогою тернарного оператора
title = age >= 18 ? 'Adult' : 'Child'

// запис за допомогою стандартного if… else
if (age >= 18) {
	console.log('Adult')
} else {
	console.log('Child')
}

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

Цикли

Нам часто потрібно повторювати однакові дії на різних даних. Наприклад, вивести на екран список продуктів. Для таких випадків існують цикли.

Один із розповсюджених базових циклів — цикл for.

Наприклад, цей код виводить у консоль цифри від 1 до 3:

// в круглих дужках прописуються умови циклу
for (let i = 1; i < 3; i = i + 1) {
	// все, що міститься у фігурних дужках, називається тілом циклу
	console.log(i)
}

// схематична робота цього циклу покроково:
let i = 1
// якщо умова i<3 виконається, виконати код та збільшити i на один крок
if (i < 3) {
	console.log(i)
	i = i + 1
}
// i = 2
if (i < 3) {
	console.log(i)
	i = i + 1
}
// i = 3
if (i < 3) {
	// за поточного значення умова не виконується, тож цикл розривається,
	// і програма виконує той код, що написаний після циклу
}

Розберемо докладніше умови виконання циклу, що прописуються в дужках:

  • Початкове значення (let i = 1) — стартова команда, яка виконується один раз перед початком циклу. Зазвичай для ітерування циклів створюється нова змінна (як і в цьому прикладі — змінна i оголошується прямо в умовах циклу), але також можливо використовувати попередньо створені змінні.
  • Умова продовження циклу (i < 3) — умова, яка перевіряється перед кожним кроком циклу. Якщо вона повертає true, виконується наступна ітерація. У даному прикладі виконується код із тіла циклу та значення «i» збільшується на 1.
  • Крок циклу (i = i + 1) — число, на яке збільшується (або зменшується, адже цикли можна використовувати і для підрахунку у зворотному порядку) ітератор циклу. Можу бути будь-яким числом.

Вираз i = i + 1 можна записати коротше за допомогою операції скороченого присвоєння — і += 1

Скорочене присвоєння можна використовувати з будь-яким числом та з операціями додавання, віднімання, множення або ділення:

і -= 10

і *= 3

і /= 6

Якщо ми хочемо відняти або додати від значення 1, то це можна записати ще коротше — і- - та і++ відповідно.

Приклад циклу зі зменшенням індексу:

for (let i = 3; i > 0; i--) {
	console.log(i)
}

Приклад циклу з кроком 3:

for (let i = 0; i < 10; i += 3) {
	console.log(i)
}

У циклу for є також дві модифікації: for... in (для перебору ключів у об’єкті) та for... of (для ітерування масивів). У цій статті вони розглядаються у відповідних розділах про об’єкти та масиви.

Ще одним базовим циклом є while.

let i = 0

while (i < 3) {
	// код у тілі циклу виконується при виконанні умови
	console.log(i)

	// важливо змінювати складову умови всередині тіла циклу
	// інакше ризикуємо отримати нескінчений цикл (умова завжди повертатиме true)
	i++
}

// приклад нескінченного циклу
while (i < 3) {
	console.log(i)
}
// такий цикл призведе до некоректної роботи, зависання, а потім крашу (закінчення роботи) програми

Якщо умова не виконується від самого початку (наприклад, i відразу дорівнює 4), то цикл не виконається жодного разу.

Існує також цикл do... while, який за механізмом роботи дуже схожий на звичайний while, проте з ключовою відмінністю

let i = 4

do {
	console.log(i)
	i++
} while (i < 3)

// код виведе в консоль 4, хоча в умові стоїть i < 3
// це ключова особливість: код у тілі циклу виконується принаймні один раз

Із самого синтаксису коду видно, що цикл спочатку виконує код, а лише потім, наприкінці першої ітерації, перевіряє виконання умови. Для 2+ ітерацій цикл do...while не відрізняється за принципом від звичайного while.

Будь-який цикл можна завчасно перервати за допомогою ключового слова break. Для простого прикладу уявімо, що ми перебираємо цифри від одного до десяти, але хочемо перервати цикл на цифрі 5:

for (let i = 1; i <= 10; i++) {
	if (i === 5) {
		// перериваємо виконання циклу при виконанні цієї умови
		break
	}
	console.log(i)
}

// результат у консолі:
// 1 2 3 4

Також існує можливість пропустити виконання тіла циклу на одній з ітерацій. Припустимо, що тепер ми хочемо на 5 не перервати виконання циклу, а пропустити вивід цифри 5 у консоль.

for (let i = 1; i <= 10; i++) {
	if (i === 5) {
		// пропускаємо крок циклу при виконанні цієї умови
		continue
	}
	console.log(i)
}

// результат у консолі:
// 1 2 3 4 6 7 8 9 10

Масиви

Регулярна задача в програмуванні — зберігати цілий набір (масив) однотипних даних. У JavaScript для цього існує окремий тип даних — масив (Array).

// масиви оголошується тільки за допомогою ключового слова const
const weekdaysArray = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']

// елементи масиву записуються через кому всередині квадратних дужок []
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
const emptyArray = []

// масив також можна створити за допомогою ключового слова new
// але на практиці цей підхід використовується рідко
const names = new Array('Mary', 'John', 'Sherlock')

Масиви є окремим випадком типу даних об’єкт (про нього детальніше поговоримо пізніше). Як уже згадувалось, об’єкти належать до не примітивних типів даних — тобто ви створюєте один масив, а потім просто вносите в нього зміни (на відміну від рядків, які при внесенні в них змін просто повністю перезаписуються як новий рядок). Саме тому масиви оголошуються лише через ключове слово const — сам масив надалі не змінюється, змінюється лише його наповнення.

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

const weekdaysArray = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']

// отримувати елемент масиву (і, наприклад, виводити його в консоль)
console.log(weekdaysArray[0])
// Monday

// заміняти елемент у масиві
weekdaysArray[1] = '2'
console.log(weekdaysArray[1])
// '2'

// додавати елементи до масиву
weekdaysArray[5] = 'Saturday'
console.log(weekdaysArray)
// ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']

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

weekdaysArray[7] = 'Saturday'
console.log(weekdaysArray)
// ['Monday','Tuesday','Wednesday','Thursday','Friday',empty,empty,'Saturday']

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

const weekdaysArray = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']

console.log(weekdaysArray.length)
// 5

// додавання елементу в кінець масиву за допомогою властивості length
weekdaysArray[weekdaysArray.length] = 'Saturday'
console.log(weekdaysArray)
// ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']

// для додавання елементів у кінець масиву можна скористатися методом push()
weekdaysArray.push('Saturday', 'Sunday')
console.log(weekdaysArray)
// ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday', 'Sunday']

// для видалення елементу з кінця масиву використовується метод pop()
weekdaysArray.pop()
console.log(weekdaysArray)
// ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']

// для додавання елементів на початок масиву можна скористатися методом unshift
weekdaysArray.unshift('Saturday', 'Sunday')
console.log(weekdaysArray)
// ['Saturday', 'Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']
// для видалення елементу з початку масиву використовується метод shift()
weekdaysArray.shift()
console.log(weekdaysArray)
// ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']

Якщо ми хочемо перебрати елементи масиву (наприклад, аби порахувати середнє арифметичне), можна скористатися модифікацією циклу for, про яку ми говорили вище:

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

// створимо змінну, куди записуватимемо суму всіх елементів масиву
let sum = 0

// цикл іде по масиву та бере кожен його елемент
for (const item of numbers) {
 // додаємо кожне число до суми
 sum += item;
}

let average = sum / numbers.length
// 5

// ми можемо використати звичайний цикл for:

sum = 0

// цикл іде по масиву та бере кожен його елемент
for (let i = 0; i < numbers.length; i++) {
 // додаємо кожне число до суми
 sum += numbers[i];
}

average = sum / numbers.length
// 5

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

У прикладах наведено найпопулярніші з них:

const numbers = [1, 2, 3]

// forEach виконує вказану функцію один раз для кожного елемента масиву
numbers.forEach(element => console.log(element))
// виводить у консоль 1 2 3

// some перевіряє, чи хоча б один елемент масиву підходить під задану умову
// і повертає відповідне значення true/false
numbers.some(element => element > 1)
// повертає true

// every перевіряє, чи всі елементи масиву підходить під задану умову
// і повертає відповідне значення true/false
numbers.every(element => element > 1)
// повертає false

// find повертає перший елемент масиву, який задовольняє задану умову
// якщо такий елемент не існує, повертає undefined
numbers.find(element => element > 1)
// повертає 2

// findIndex повертає індекс першого елементу масиву, який задовольняє умову
// якщо такий елемент не існує, повертає -1
numbers.findIndex(element => element > 1)
// повертає 1

// filter повертає новий масив, у яких входять елементи оригінального масиву
// які задовольнили умови фільтрування
const newNumbers = numbers.filter(element => element > 1)
console.log(newNumbers)
// [2, 3]

// sort сортує вже існуючий масив за заданим принципом
numbers.sort((num1, num2)=> num2 — num1)
console.log(numbers)
// [3, 2, 1]
// якщо принцип сортування не заданий, метод сортує елементи в порядку зростання
numbers.sort()
console.log(numbers)
// [1, 2, 3]

// map викликає задану функцію для кожного елементу
// та замінює сам елемент на результат виконання функції
const newNumbers = numbers.map(element => element + 1)
console.log(newNumbers)
// [2,3,4]
// reduce — достатньо складний метод, який найпростіше пояснити відразу на прикладі
// знайдемо суму всіх елементів масиву за допомогою цього методу

let initialValue = 0
// reduce повертає одне значення, у нашому випадку — суму всіх елементів масиву
const sum = numbers.reduce(
	// accumulator — це змінна, у якій зберігається значення виконання заданої
	// функції з усіма попередніми елементами масиву
	// тобто в нашому випадку — суму всіх попередніх елементів масиву
	// currentValue — поточний елемент масиву
 (accumulator, currentValue) => accumulator + currentValue,
	// initialValue — значення, яке міститься в accumulator на першій ітерації
	// в нашому прикладі це 0
 initialValue
)
console.log(sum)
// 6

Зверніть увагу, що частина методів (наприклад, sort) змінюють початковий масив (їх називають мутуючими), а частина — вертає новий масив. Про це важливо пам’ятати в роботі, адже це впливає на підхід до завдання. Іноді, наприклад, необхідно зберегти початковий масив незмінним, тому краще обрати немутуючий метод або скопіювати масив (наприклад, за допомогою методу slice без параметрів) і змінювати його копію.

Більше прикладів та методів для роботи з масивами можна знайти за посиланням.

Тривіальна задача в програмуванні — розбити рядок із якимись даними на масив окремих елементів (іноді після цього певні елементи потрібно знову зібрати в рядок). Для розв’язання цих проблем часто використовують методи split() та join():

// початковий рядок
const week = 'Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday'
// результат, якого ми хочемо досягнути
const expextedResult = 'Monday — Tuesday — Wednesday — Thursday — Friday'

// split() розбиває рядок на окремі елементи масиву
// приймає один аргумент-символ, за яким потрібно здійснити розбивання
// якщо аргумент не передано, таким символом вважатиметься пробіл
// в нашому випадку це буде кома та пробіл після неї, щоб елементом
// отриманого масиву була чиста назва дня без зайвих знаків

const days = week.split(', ')
console.log(days)
// ['Monday', ' Tuesday', ' Wednesday', ' Thursday', ' Friday', ' Saturday', ' Sunday']

// скористаємось методом slice, який працює і для масивів, і для рядків,
// аби прибрати вихідні дні
const weekdaysArray = days.slice(0,5)
console.log(weekdaysArray)
// ['Monday', ' Tuesday', ' Wednesday', ' Thursday', ' Friday']

// join() поєднує елементи масиву в рядок. За аналогією зі split(),
// метод приймає аргумент-символ, яким поєднуватимуться елементи
// в нашому випадку це тире
const result = weekdaysArray.join(' — ')
console.log(result)

Обʼєкти

І, нарешті, поговоримо про об’єкти в JavaScript, без яких не обходиться жодна реальна програма.

Фактично об’єкти зберігають набір характеристик у форматі ключ-значення:

// приклад об'єкта
const student = {
	name: 'Taras',
	surname: 'Shevchenko',
	age: 23,
	classes: ['Literature', 'Philosophy', 'Art'],
	hasGoodGrades: true,
	fullName: function() {
    return this.name + " " + this.surname;
  }
}

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

Викликаються методи об’єктів, як звичайні функції:

const student = {
	name: 'Taras',
	surname: 'Shevchenko',
	age: 23,
	classes: ['Literature', 'Philosophy', 'Art'],
	hasGoodGrades: true,
	fullName: function() {
    return this.name + " " + this.surname;
  }
}

console.log(student.fullName())
// Taras Shevchenko

// якщо не поставити (), тобто не викликати функцію, то в консоль виведеться
// значення поля, тобто сам текст функції
console.log(student.fullName)
/*
	ƒ () {
    return this.name + " " + this.surname;
  }
*/

У JavaScript слово this є ключовим і позначає об’єкт, у методі якого він був викликаний (у прикладі вище це об’єкт student). Якщо this використовується поза межами методів конкретного об’єкта, то він посилається на глобальний об’єкт. Детальніше про this можна прочитати в документації.

Тема роботи з об’єктами — дуже широка, у рамках цієї статті ми розберемо лише базові операції роботи з полями об’єкта:

const student = {
	name: 'Taras',
	surname: 'Shevchenko',
	age: 23,
	classes: ['Literature', 'Philosophy', 'Art'],
	hasGoodGrades: true,
	fullName: function() {
    return this.name + " " + this.surname;
  }
}

// зчитування поля об'єкта можливе двома способами
console.log(student.name)
// Таrаs
console.log(student["name"])
// Таrаs

// другий спосіб зазвичай використовується, якщо ми як назву поля
// використовуємо іншу змінну (наприклад, при ітеруванні по об'єкту)
// якщо нам потрібно пройти по всім ключам і значенням об'єкта, можна
// скористатися циклом for… in (цикл for… of працює лише для масивів)
for (key in student) {
	console.log(student[key])
}
/*
	Taras
	Shevchenko
	23
	['Literature', 'Philosophy', 'Art']
	true
	ƒ () {
    return this.name + " " + this.surname;
  }
*/

// за допомогою такого синтаксису можна також перезаписувати значення полів
student.name = 'Kateryna'
console.log(student.name)
// Kateryna

// або додавати нові
student.averageGrade = 5
console.log(student)
/*
	name: 'Taras',
	surname: 'Shevchenko',
	age: 23,
	classes: ['Literature', 'Philosophy', 'Art'],
	hasGoodGrades: true,
	fullName: ƒ () {
    return this.name + " " + this.surname;
  },
	averageGrade: 5
*/
// для видалення поля можна скористатися методом delete
delete student.averageGrade
console.log(student)
/*
	name: 'Taras',
	surname: 'Shevchenko',
	age: 23,
	classes: ['Literature', 'Philosophy', 'Art'],
	hasGoodGrades: true,
	fullName: ƒ () {
    return this.name + " " + this.surname;
  }
*/

Більше про об’єкти та їхні методи можна прочитати в документації.

Замість висновків

Найліпший (і єдиний) спосіб зрозуміти теорію — відточити її на практиці, тому пропоную почати практикувати прямо зараз. Наприклад, за допомогою Codewars — це ресурс, де зібрано безліч задач для різних мов програмування, рівнів складності та тематики. За посиланням знайдете список задач для JavaScript найлегшого рівня, відфільтрованих за темами «Рядки», «Масиви» та «Основи». До справи 😉

👍ПодобаєтьсяСподобалось22
До обраногоВ обраному13
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Зверніть, будь ласка, увагу на помилки в прикладах до пунктів
1. localeCompare() - рядок другий прикладу (має бути «позитивне значення»);
2. Функції — в блоці з прикладом isHungry та hasEnoughMoney (останні рядки мають значення true, але судячи з опису, там має бути false)

Важливо пам’ятати, що в гарно написаному коді з назви змінної зрозуміло, які саме дані в ній зберігаються
cancelledPrice

«У JavaScript змінні оголошуються за допомогою двох кодових слів — let (якщо може змінити своє значення під час виконання програми) та const (коли значення присвоюється один раз при оголошенні).»
Не тільки значення, а і референс. Тому можна змінювати об’єкти які ідуть як const. У расті наприклад можна явно вказати чи референс мутейбл

Дякую за коментар!
Дуже влучне зауваження, але на мій погляд, референс може заплутати новачків.
У цій статті я намагалась зібрати базову теорію для тих, хто тільки-тільки починає вивчати Javascript з нуля або майже з нуля. Таке поняття, як референс, трошки складніше для розуміння, як на мене, і вже вимагає мінімальних знань :)

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

Ти плутаєш ключ об’єкта і значення. Ключі — стрінга, або символ. Значення — будь-шо.
Не дивився все, але бачу багато доречних зауважень по статті і як на мене вона може бути шкідливою новачку.
Можно просто дати посилання сюди uk.javascript.info та й все на цьому. А взагалі, якщо чекати поки все перекладуть на українську, то так ніколи не встигнеш за технологіями і не знайдеш відповіді на виникаючі питання. Бажано разом з джсом вчити англ.

на кшталт есть лерн джавачкрипт, который в итоге и будут читать

console.log(’typeof’ (age)); // ’number’

Uncaught TypeError: “typeof” is not a function

Дякую, що помітили! Це одруківка при верстці статті, вже виправили :)

В секції логічних операторів, не вистачає умовно-нових Nullish coalescing operator (??) і Nullish coalescing assignment (??=). Доволі корисні, насправді.

Дякую за зауваження!
Я в цій статті зібрали лише самі базові поняття, які при цьому постійно використовую у роботі.
Ці два оператори особисто я використовую рідко, але вони дійсно корисні в окремих кейсах :)

Не скрин екрана, а світлина стільниці

Array (масив) — набір даних одного типу (наприклад, список продуктів був би масивом рядків).

Мені здається, що в JS array не виоокремлюється в окремий тип даних, а належить до object. Що власне, трохи пізніше в статті і згадується:

Масиви є окремим випадком типу даних об’єкт

Схоже array опинився в списку типів даних випадково.

Та й власне не обов’язково:

одного типу

Обов’язково, тоді то вже вектор, а не array

Ну, це вже інтерпретація. Мова дозволяє мати сутність типу array із членами (?) різного типу.

Враховуючи, що у масива є свої властивості та методи, не притамані іншим об’єктам, окремо виділити його цілком має сенс.

У function, Date, ArrayBuffer,... також

є свої властивості та методи, не притамані іншим об’єктам

Як захотіти, то в кожному рандомному абзаці можна до чогось причепитися:

Якщо this використовується поза межами методів конкретного об’єкта, то він посилається на глобальний об’єкт.
скористатися циклом for... in (цикл for... of працює лише для масивів)

Дякую за коментар! Насправді, коли я тільки починала вивчати Javascript, то теж не виносила масиви в окремий тип даних. Але при підготовці цієї статті помітила, що наразі багато джерел виносять їх як окремий вид даних.

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

Разом з тим варто пам’ятати, що це все ж таки об’єкти, що і вказано у другій цитаті з вашого коментаря :)

Натомість наступні шість типів є дуже розповсюдженими:

Можливо тоді не 6, а 7?

вот то, на основе чего пишутся многие статьи и учебники. tc39.es/ecma262/2023

An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Symbol, Number, BigInt, and Object. An ECMAScript language value is a value that is characterized by an ECMAScript language type.

Підписатись на коментарі