Ми хочемо зробити цей проєкт з відкритим кодом доступним для людей у всьому світі.

Допоможіть перекласти цей підручник вашою мовою!

Базові оператори, математика

Зі шкільної програми ми знаємо багато арифметичних операцій, таких як додавання +, множення *, віднімання - тощо.

У цьому розділі ми почнемо з простих операторів, потім зосередимося на специфічних для JavaScript аспектах, які не охоплені шкільною арифметикою.

Терміни: “унарний”, “бінарний”, “операнд”

Перш ніж ми почнемо, розберімо певну загальну термінологію.

  • Операнд – це те, до чого застосовуються оператори. Наприклад, у множенні 5 * 2 є два операнди: лівий операнд 5 і правий операнд 2. Іноді їх називають “аргументами”, а не “операндами”.

  • Оператор є унарним, якщо він має один операнд. Наприклад, унарне заперечення - змінює знак числа:

    let x = 1;
    
    x = -x;
    alert( x ); // -1, було застосоване унарне заперечення
  • Оператор є бінарним, якщо він має два операнди. Наприклад, оператор мінус можна використовувати та у бінарній формі:

    let x = 1, y = 3;
    alert( y - x ); // 2, бінарний мінус віднімає значення

    Формально, у прикладах вище ми маємо два різні оператори, які позначаються однаковим символом: оператор заперечення – унарний оператор, який змінює знак числа, та оператор віднімання – бінарний оператор, який віднімає одне число від іншого.

Математика

JavaScript підтримує такі математичні операції:

  • Додавання +,
  • Віднімання -,
  • Множення *,
  • Ділення /,
  • Остача від ділення %,
  • Піднесення до степеня **.

Перші чотири операції зрозумілі, а от про % та ** потрібно сказати декілька слів.

Остача від ділення %

Оператор остачі %, попри свій зовнішній вигляд, не пов’язаний із відсотками.

Результатом a % b є остача цілочислового ділення a на b.

Наприклад:

alert( 5 % 2 ); // 1 - остача від ділення 5 на 2
alert( 8 % 3 ); // 2 - остача від ділення 8 на 3
alert( 8 % 4 ); // 0 - остача від ділення 8 на 4

Піднесення до степеня **

Оператор піднесення до степеня a ** b множить a саме на себе b разів.

У школі ми записуємо це як ab.

Наприклад:

alert( 2 ** 2 ); // 2² = 4
alert( 2 ** 3 ); // 2³ = 8
alert( 2 ** 4 ); // 2⁴ = 16

Так само як у математиці, оператор піднесення також можна використовувати для дробових чисел.

Наприклад, квадратний корінь це піднесення до степеня ½:

alert( 4 ** (1/2) ); // 2 (степінь 1/2 — це теж саме, що квадратний корінь)
alert( 8 ** (1/3) ); // 2 (степінь 1/3 — це теж саме, що кубічний корінь)

Об’єднання рядків через бінарний +

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

Зазвичай оператор плюс + додає числа.

Але якщо бінарний + застосовується до рядків, він об’єднує їх:

let s = 'мій_' + 'рядок';
alert(s); // мій_рядок

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

Наприклад:

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

Бачите, не має значення, чи перший операнд – рядок, чи другий.

Ось складніший приклад:

alert(2 + 2 + '1' ); // "41", а не "221"

Тут оператори виконуються один за одним. Перший + додає два числа, тому він поверне 4; а наступний оператор + вже додасть (об’єднає) попередній результат із рядком 1. У підсумку ми отримаємо рядок '41' (4 + '1').

alert('1' + 2 + 2); // "122", а не "14"

У цьому прикладі перший операнд – рядок, тому компілятор також опрацьовує інші два операнди як рядки. Операнд 2 приєднується (конкатенується) до '1', тому в результаті буде '1' + 2 = "12", а потім — "12" + 2 = "122".

Лише бінарний + працює з рядками так. Інші арифметичні оператори працюють тільки з числами й завжди перетворюють свої операнди на числа.

Ось приклад, як працює віднімання й ділення:

alert( 6 - '2' ); // 4, '2' перетворюється на число
alert( '6' / '2' ); // 3, обидва операнди перетворюються на числа

Числове перетворення, унарний +

У оператора плюс + є дві форми: бінарна, яку ми використовували вище, та унарна.

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

Наприклад:

// Нема ніякого впливу на числа
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// Перетворює нечислові значення
alert( +true ); // 1
alert( +"" );   // 0

Він насправді працює як і Number(...), але має коротший вигляд.

Необхідність перетворення рядків на числа виникає дуже часто. Наприклад, якщо ми отримуємо значення з полів HTML форми, вони зазвичай є рядками. Що робити, якщо ми хочемо їх підсумувати?

Бінарний плюс додав би їх як рядки:

let apples = "2";
let oranges = "3";

alert( apples + oranges ); // "23", бінарний плюс об’єднує рядки

Якщо ми хочемо використовувати їх як числа, нам потрібно конвертувати, а потім підсумувати їх:

let apples = "2";
let oranges = "3";

// обидва значення перетворюються на числа перед застосуванням бінарного плюса
alert( +apples + +oranges ); // 5

// довший варіант
// alert( Number(apples) + Number(oranges) ); // 5

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

Чому унарні плюси застосовуються до значень перед бінарними плюсами? Як ми побачимо далі, це пов’язано з їхнім вищим пріоритетом.

Пріоритет оператора

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

Зі школи ми всі знаємо, що множення у виразі 1 + 2 * 2 має бути обчислене перед додаванням. Саме це і є пріоритетом. Кажуть, що множення має вищий пріоритет, ніж додавання.

Дужки перевизначають будь-який пріоритет, тому, якщо ми не задоволені типовим пріоритетом, ми можемо використовувати дужки, щоби змінити його. Наприклад: (1 + 2) * 2.

У JavaScript є багато операторів. Кожен оператор має відповідний номер пріоритету. Першим виконується той оператор, який має найбільший номер пріоритету. Якщо пріоритет є однаковим, порядок виконання — зліва направо.

Ось витяг із таблиці пріоритетів (вам не потрібно її запам’ятовувати, але зверніть увагу, що унарні оператори мають вищий пріоритет за відповідні бінарні):

Пріоритет Ім’я Знак
14 унарний плюс +
14 унарний мінус -
13 піднесення до степеня **
12 множення *
12 ділення /
11 додавання +
11 віднімання -
2 присвоєння =

Як ми бачимо, “унарний плюс” має пріоритет 14, що вище за 11 – пріоритет “додавання” (бінарний плюс). Саме тому, у виразі "+apples + +oranges", унарні плюси виконуються перед додаванням (бінарним плюсом).

Присвоєння

Зазначимо, що присвоєння = також є оператором. Воно є у таблиці з пріоритетами й має дуже низький пріоритет 2.

Тому, коли ми присвоюємо значення змінній, наприклад, x = 2 * 2 + 1, спочатку виконуються обчислення, а потім виконується присвоєння = зі збереженням результату в x.

let x = 2 * 2 + 1;

alert( x ); // 5

Присвоєння = повертає результат

Той факт, що = є оператором, а не “магічною” конструкцією мови, має цікаве значення.

Усі оператори в JavaScript повертають значення. Це очевидно для + та -, але це також правдиво для =.

Виклик x = значення записує значення у x, а потім повертає його.

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

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

У наведеному вище прикладі результат виразу (a = b + 1) є значенням, яке присвоювалося змінній a (тобто 3). Потім воно використовується для подальших обчислень.

Чудернацький код, чи не так? Ми маємо розуміти, як це працює, бо іноді ми бачимо подібне в бібліотеках JavaScript.

Однак, будь ласка, не пишіть свій код так. Ці трюки, безумовно, не роблять код більш зрозумілим або читабельним.

Ланцюгові присвоєння

Іншою цікавою особливістю є здатність ланцюгового присвоєння:

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

Ланцюгове присвоєння виконується справа наліво. Спочатку обчислюється найправіший вираз 2 + 2, а потім результат присвоюється змінним ліворуч: c, b та a. Зрештою всі змінні мають спільне значення.

Знову таки, щоби покращити читабельність коду, краще розділяти подібні конструкції на декілька рядків:

c = 2 + 2;
b = c;
a = c;

Так легше прочитати, особливо коли швидко переглядати код.

Оператор “модифікувати та присвоїти”

Часто нам потрібно застосувати оператор до змінної й зберегти новий результат у ту ж саму змінну.

Наприклад:

let n = 2;
n = n + 5;
n = n * 2;

Цей запис можна скоротити за допомогою операторів += та *=:

let n = 2;
n += 5; // тепер n = 7 (те ж саме, що n = n + 5)
n *= 2; // тепер n = 14 (те ж саме, що n = n * 2)

alert( n ); // 14

Короткі оператори “модифікувати та присвоїти” є для всіх арифметичних та побітових операторів: /=, -= тощо.

Ці оператори мають такий же пріоритет, як і звичайне присвоєння, тому вони виконуються після більшості інших обчислень:

let n = 2;

n *= 3 + 5; // права частина обчислюється першою, так само як і n *= 8

alert( n ); // 16

Інкремент/декремент

Збільшення або зменшення на одиницю є однією з найпоширеніших числових операцій.

Тому для цього є спеціальні оператори:

  • Інкремент ++ збільшує змінну на 1:

    let counter = 2;
    counter++;        // працює так само, як counter = counter + 1, але запис коротше
    alert( counter ); // 3
  • Декремент -- зменшує змінну на 1:

    let counter = 2;
    counter--;        // працює так само, як counter = counter - 1, але запис коротше
    alert( counter ); // 1
Важливо:

Інкремент/декремент можуть застосовуватися лише до змінних. Спроба використати їх із значенням, як от 5++, призведе до помилки.

Оператори ++ та -- можуть розташовуватися до або після змінної.

  • Коли оператор йде за змінною, він у “постфіксній формі”: counter++.
  • “Префіксна форма” – це коли оператор йде попереду змінної: ++counter.

Обидві ці інструкції роблять те ж саме: збільшують counter на 1.

Чи є різниця? Так, але ми можемо побачити її тільки використавши значення, яке повертають ++/--.

Розберімось. Як нам відомо, всі оператори повертають значення. Інкремент/декремент не є винятком. Префіксна форма повертає нове значення, тоді як постфіксна форма повертає старе значення (до збільшення/зменшення).

Щоби побачити різницю, наведемо приклад:

let counter = 1;
let a = ++counter; // (*)

alert(a); // 2

У рядку (*), префіксна форма ++counter збільшує counter та повертає нове значення, 2. Отже, alert показує 2.

Тепер скористаємося постфіксною формою:

let counter = 1;
let a = counter++; // (*) змінили ++counter на counter++

alert(a); // 1

У рядку (*), постфіксна форма counter++ також збільшує counter, але повертає старе значення (до інкременту). Отже, alert показує 1.

Підсумки:

  • Якщо результат збільшення/зменшення не використовується, немає ніякої різниці, яку форму використовувати:

    let counter = 0;
    counter++;
    ++counter;
    alert( counter ); // 2, у рядках вище робиться одне і те ж саме
  • Якщо ми хочемо збільшити значення та негайно використати результат оператора, нам потрібна префіксна форма:

    let counter = 0;
    alert( ++counter ); // 1
  • Якщо ми хочемо збільшити значення, але використати його попереднє значення, нам потрібна постфіксна форма:

    let counter = 0;
    alert( counter++ ); // 0
Інкремент/декремент серед інших операторів

Оператори ++/-- також можуть використовуватися всередині виразів. Їхній пріоритет вищий за більшість інших арифметичних операцій.

Наприклад:

let counter = 1;
alert( 2 * ++counter ); // 4

Порівняйте з:

let counter = 1;
alert( 2 * counter++ ); // 2, тому що counter++ повертає "старе" значення

Хоча з технічного погляду це допустимо, такий запис робить код менш читабельним. Коли один рядок робить кілька речей – це не добре.

Під час читання коду швидке “вертикальне” сканування оком може легко пропустити щось подібне до counter++, і не буде очевидним, що змінна була збільшена.

Ми рекомендуємо стиль “одна лінія – одна дія”:

let counter = 1;
alert( 2 * counter );
counter++;

Побітові оператори

Побітові оператори розглядають аргументи як 32-бітні цілі числа та працюють на рівні їхнього двійкового представлення.

Ці оператори не є специфічними для JavaScript. Вони підтримуються у більшості мов програмування.

Список операторів:

  • AND(і) ( & )
  • OR(або) ( | )
  • XOR(побітове виключне або) ( ^ )
  • NOT(ні) ( ~ )
  • LEFT SHIFT(зсув ліворуч) ( << )
  • RIGHT SHIFT(зсув праворуч) ( >> )
  • ZERO-FILL RIGHT SHIFT(зсув праворуч із заповненням нулями) ( >>> )

Ці оператори використовуються тоді, коли нам потрібно “возитися” з числами на дуже низькому (побітовому) рівні (тобто – вкрай рідко). Найближчим часом такі оператори нам не знадобляться, оскільки у веброзробці вони майже не використовуються. Проте в таких галузях, як криптографія, вони можуть бути дуже корисними. Ви можете прочитати розділ Bitwise Operators на MDN, якщо виникне потреба.

Кома

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

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

Наприклад:

let a = (1 + 2, 3 + 4);

alert( a ); // 7 (результат обчислення 3 + 4)

Тут обчислюється перший вираз 1 + 2 і його результат викидається. Потім обчислюється 3 + 4 і повертається як результат.

Кома має дуже низький пріоритет

Зверніть увагу, що оператор “кома” має дуже низький пріоритет, нижчий за =, тому дужки є важливими в наведеному вище прикладі.

Без дужок, у виразі a = 1 + 2, 3 + 4 спочатку обчислюються оператори +, підсумовуючи числа у a = 3, 7; потім оператор присвоєння = присвоює a = 3, а решта (число 7 після коми) ігнорується. Це як записати вираз (a = 1 + 2), 3 + 4.

Чому нам потрібен оператор, що викидає все, окрім останнього виразу?

Іноді його використовують у складніших конструкціях, щоби помістити кілька дій в один рядок.

Наприклад:

// три операції в одному рядку
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

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

Завдання

важливість: 5

Які кінцеві значення всіх змінних a, b, c та d після виконання коду нижче?

let a = 1, b = 1;

let c = ++a; // ?
let d = b++; // ?

Відповідь:

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, префіксна форма повертає нове значення
alert( b++ ); // 1, постфіксна форма повертає старе значення

alert( a ); // 2, збільшується один раз
alert( b ); // 2, збільшується один раз
важливість: 3

Які значення мають a та x після виконання коду нижче?

let a = 2;

let x = 1 + (a *= 2);

Відповідь:

  • a = 4 (помножиться на 2)
  • x = 5 (обчислюється як 1 + 4)
важливість: 5

Які результати цих виразів?

"" + 1 + 0
"" - 1 + 0
true + false
6 / "3"
"2" * "3"
4 + 5 + "px"
"$" + 4 + 5
"4" - 2
"4px" - 2
"  -9  " + 5
"  -9  " - 5
null + 1
undefined + 1
" \t \n" - 2

Добре подумайте, запишіть, а потім порівняйте з відповіддю.

"" + 1 + 0 = "10" // (1)
"" - 1 + 0 = -1 // (2)
true + false = 1
6 / "3" = 2
"2" * "3" = 6
4 + 5 + "px" = "9px"
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
"  -9  " + 5 = "  -9  5" // (3)
"  -9  " - 5 = -14 // (4)
null + 1 = 1 // (5)
undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
  1. Додавання пустого рядка "" + 1 перетворює число 1 на рядок: "" + 1 = "1"; далі ми маємо "1" + 0, де застосовується те ж саме правило.
  2. Віднімання - (як і більшість математичних операцій) працює тільки з числами, воно перетворює порожній рядок "" на 0.
  3. Додавання з рядком додає число 5 до рядка.
  4. Віднімання завжди перетворює на числа, тому рядок " -9 " перетвориться на число -9 (ігноруючи пробіли навколо нього).
  5. null стає 0 після числового перетворення.
  6. undefined стає NaN після числового перетворення.
  7. Символи пробілів по краях рядка ігноруються під час перетворення в число. Тому рядок, який містить лише символи \t, \n або «звичайні» пробіли, прирівнюється до пустого рядка і стає 0 після числового перетворення.
важливість: 5

Нижче наведено код, що просить користувача ввести два числа і відображає їхню суму.

Він працює неправильно. Код у прикладі виводить 12 (для початкових значень у полях вводу).

У чому помилка? Виправте її. Результат має бути 3.

let a = prompt("Перше число?", 1);
let b = prompt("Друге число?", 2);

alert(a + b); // 12

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

Отже, змінні отримують значення "1" і "2" відповідно.

let a = "1"; // prompt("Перше число?", 1);
let b = "2"; // prompt("Друге число?", 2);

alert(a + b); // 12

Нам треба перетворити рядки на числа перед застосуванням оператора +. Наприклад, за допомогою Number() або вставлення + перед ними.

Вставити + можна безпосередньо перед prompt:

let a = +prompt("Перше число?", 1);
let b = +prompt("Друге число?", 2);

alert(a + b); // 3

Або всередині alert:

let a = prompt("Перше число?", 1);
let b = prompt("Друге число?", 2);

alert(+a + +b); // 3

В останньому варіанті унарний і бінарний + використовуються разом. Виглядає химерно, чи не так?

Навчальна карта

Коментарі

прочитайте це, перш ніж коментувати…
  • Якщо у вас є пропозиції, щодо покращення підручника, будь ласка, створіть обговорення на GitHub або одразу створіть запит на злиття зі змінами.
  • Якщо ви не можете зрозуміти щось у статті, спробуйте покращити її, будь ласка.
  • Щоб вставити код, використовуйте тег <code>, для кількох рядків – обгорніть їх тегом <pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)