16 липня 2023 р.

Застаріле ключове слово "var"

Ця стаття призначена для розуміння старих скриптів

Інформація в цій статті корисна для розуміння старих скриптів.

Зараз так код не пишуть.

У першому розділі про змінні, ми ознайомились з трьома способами оголошення змінних:

  1. let
  2. const
  3. var

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

var message = "Привіт";
alert(message); // Привіт

Але var – це зовсім інший звір, який походить з дуже давніх часів. Зазвичай він не використовується в сучасних скриптах, але все ще може переховуватися у старих.

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

З іншого боку, важливо розуміти відмінності при міграції старих скриптів з var на let, щоб уникати зайвих помилок.

Для “var” не існує блочної області видимості

Змінні, оголошені за допомогою var, мають або функціональну, або глобальну область видимості. Вони видимі за межами блоку.

Наприклад:

if (true) {
  var test = true; // використовуємо "var" замість "let"
}

alert(test); // true, змінна існує поза блоком if

Так як var ігнорує блоки, ми отримали глобальну змінну test.

Якщо б ми використали let test замість var test, тоді змінна була б видима тільки всередині if:

if (true) {
  let test = true; // використовуємо "let"
}

alert(test); // ReferenceError: test не визначена

Те саме і для циклів: змінна, оголошена за допомогою var, не може бути блочною або локальною всередині цикла:

for (var i = 0; i < 10; i++) {
  var one = 1;
  // ...
}

alert(i);   // 10, "i" видима за межами циклу, це глобальна змінна
alert(one); // 1, "one" видима за межами циклу, це глобальна змінна

Якщо блок коду знаходиться всередині функції, тоді var стає змінною рівня функції:

function sayHi() {
  if (true) {
    var phrase = "Привіт";
  }

  alert(phrase); // спрацьовує
}

sayHi();
alert(phrase); // ReferenceError: phrase не визначена

Як ми бачимо, var виходить за межі if, for або інших блоків коду. Так відбувається тому, що колись блоки у Javascript не мали лексичного середовища, тому var – це пережиток минулого.

“var” терпить повторні оголошення

Якщо ми оголосимо одну і ту ж змінну за допомогою let двічі в одній області видимості, матимемо помилку:

let user;
let user; // SyntaxError: 'user' вже оголошена

З var, ми можемо повторно оголошувати змінну безліч разів. Якщо ми використовуємо var із вже оголошенною змінною, воно просто ігнорується:

var user = "Петро";

var user = "Іван"; // цей "var" нічого не робить (змінна вже оголошена). Зміниться лише значення
// ...помилки не виникне

alert(user); // Іван

Змінні “var” можуть бути оголошені після їх використання

Оголошення за допомогою var обробляються при запуску функції (або скрипта для глобальних змінних).

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

Отже, цей код:

function sayHi() {
  phrase = "Привіт";

  alert(phrase);

  var phrase;
}
sayHi();

…технічно такий самий, як і цей (var phrase переміщена вище):

function sayHi() {
  var phrase;

  phrase = "Привіт";

  alert(phrase);
}
sayHi();

…Або навіть як цей (пам’ятайте, блоки коду ігноруються):

function sayHi() {
  phrase = "Привіт"; // (*)

  if (false) {
    var phrase;
  }

  alert(phrase);
}
sayHi();

Таку поведінку називають “підняттям”, оскільки всі var “піднімаються” на початок функції.

Отже, у прикладі, наведеному вище, if (false) не виконується, але це не має значення. var всередині нього обробляється на початку функції, так що у момент (*) змінна існує.

Оголошення змінних піднімаються, але присвоєння значень – ні.

Краще продемонструвати це на прикладі:

function sayHi() {
  alert(phrase);

  var phrase = "Привіт";
}

sayHi();

Рядок var phrase = "Привіт" складається з двох дій:

  1. Оголошення змінної var
  2. Присвоєння значення змінній =.

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

function sayHi() {
  var phrase; // оголошення змінної спрацьовує спочатку...

  alert(phrase); // undefined

  phrase = "Привіт"; // ...присвоєння - в момент, коли виконується даний рядок коду.
}

sayHi();

Оскільки всі оголошення var обробляються при запуску функції, ми можемо посилатися на них у будь-якому місці. Але всі змінні мають значення undefined до оголошення.

В обох прикладах, наведених вище, alert спрацьовує без помилок, тому що змінна phrase існує. Але значення їй ще не було присвоєне, тому воно показує undefined.

IIFE

Раніше, оскільки існував тільки var, і він не мав видимості на рівні блоків, програмісти знайшли спосіб емулювати її. Те, що вони зробили, мало назву “вирази функцій, що викликаються негайно” (immediately-invoked function expressions – IIFE).

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

IIFE виглядає так:

(function() {

  var message = "Привіт";

  alert(message); // Привіт

})();

Тут створюється і негайно викликається Function Expression (Функціональний вираз). Таким чином, код виконується відразу і має власні приватні змінні.

Function Expression обгортається дужками(function {...}), тому що коли рушій JavaScript зустрічає "function" в основному коді, він розуміє це як початок Function Declaration. Але Function Declaration повинна мати назву, тому такий код викличе помилку:

// Намагається оголосити і негайно викликати функцію
function() { // <-- SyntaxError: Оператори функцій вимагають назви

  var message = "Привіт";

  alert(message); // Привіт

}();

Навіть якщо ми скажемо: “добре, додамо назву”, це не спрацює, оскільки JavaScript не дозволяє негайно викликати Function Declarations:

// синтаксична помилка через дужки нижче
function go() {

}(); // <-- не може негайно викликати Function Declaration

Отже, дужки навколо функції – це хитрість, щоб показати JavaScript, що функція створена в контексті іншого виразу, а отже, це Function Expression: вона не потребує назви і її можна викликати негайно.

Крім дужок, існують інші способи повідомити JavaScript, що ми маємо на увазі Function Expression:

// Способи створення IIFE

(function() {
  alert("Дужки навколо функції");
})();

(function() {
  alert("Круглі дужки навколо всього");
}());

!function() {
  alert("Побітовий оператор NOT запускає вираз");
}();

+function() {
  alert("Унарний плюс запускає вираз");
}();

У всіх вищенаведених випадках ми оголошуємо Function Expression і негайно запускаємо його. Зауважимо ще раз: нині немає причин писати такий код.

Підсумки

Існує дві основні відмінності між var та let/const:

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

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

Ці відмінності роблять var гірше, ніж let у більшості випадків. Блочна область видимості – гарна річ. Ось чому let давно був введений до стандарту і тепер є основним способом (разом з const) оголошування змінних.

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