Зі шкільної програми ми знаємо багато арифметичних операцій, таких як додавання +, множення *, віднімання - тощо.
У цьому розділі ми почнемо з простих операторів, потім зосередимося на специфічних для 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. Саме тому ми їх згадуємо. Але зазвичай вони не покращують читабельність коду, тому ми маємо добре подумати перед їх використанням.
Коментарі
<code>, для кількох рядків – обгорніть їх тегом<pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)