Переменные являются фундаментальной частью многих языков программирования и одними из первых и наиболее важных понятий, которые должны знать начинающие программисты. В JavaScript существует ряд различных свойств переменных, а также несколько правил, которые необходимо соблюдать при их именовании. Для объявления переменной используются три ключевых слова: var, let и const, и каждое из них влияет на то, как код будет интерпретировать переменную.

Это руководстве о том, что такое переменные, как их объявлять и называть, в чём разница между var, let и const, что такое поднятие (хоистинг), его влияние и значение глобальной и локальной области видимости на поведение переменной.

Понятие переменной

Переменная - это именованный контейнер, используемый для хранения значений. Часть информации, на которую мы можем ссылаться несколько раз, может быть сохранена в переменной для дальнейшего использования или модификации. В JavaScript значение, содержащееся внутри переменной, может быть любым типом данных, включая число, строку или объект.

До спецификации ECMAScript 2015 (ES6), на которой основан современный JavaScript, был только один способ объявить переменную - с помощью ключевого слова var. В результате, в большинстве старых кодов и учебных ресурсов для переменных используется только var. Различия между ключевыми словами var, let и const рассмотрим в отдельном разделе ниже.

Для начала, будем использовать var для демонстрации концепции самой переменной. В приведенном ниже примере мы объявим переменную и присвоим ей значение:

var username = 'Петров';

Такая запись состоит из следующих частей:

  • Объявления (декларирования) переменной с использованием ключевого слова var
  • Имени переменной (или идентификатора), username
  • Операции присваивания, представленной синтаксисом =
  • Присваиваемого значения "Петров"

Теперь мы можем использовать username в коде. JavaScript запомнит, что username представляет собой строковое значение "Петров".

// Можем проверить, равно ли значение переменной данному
if (username === 'Петров') {
  console.log(true);  // => В консоли получим true - да, равно
}

Как упоминалось ранее, переменные могут использоваться для представления любого типа данных JavaScript. В примере ниже объявим переменные со строковым, числовым, объектными, булевым значениями и значением null.

var name = 'Иван';
var age = 25;
var fishes = [ 'карась', 'окунь', 'щука' ];
var цветы = { rose: 'красный', camomile: 'белый' };
var success = true;
var nothing = null;

Используя console.log, мы можем увидеть значение, содержащееся в конкретной переменной.

console.log(age);  // => 25

Переменные хранят данные в памяти, и к ним впоследствии можно получить доступ и изменить. Переменным также можно присвоить новое значение. Упрощенный пример ниже демонстрирует, как пароль может быть сохранен в переменной, а затем обновлен:

var password = 'hunter2';

// Переопределяем значение переменной на новое
password = 'hunter3';

console.log(password);  // => 'hunter3'

Этот пример иллюстрирует ситуацию, в которой нам может потребоваться обновить значение переменной. С этой строки и далее до следующей модификации значение password будет равно "hunter3".

Именование переменных

Имена переменных в JavaScript называются идентификаторами. Краткий свод правил присвоения имени переменной:

  • Имена переменных могут состоять только из букв (a-z), цифр (0-9), символов знака доллара ($) и подчеркивания (_);
  • Имена переменных не могут содержать пробелов и табуляции;
  • Переменная не может начинаться с числа;
  • Имя переменной не может быть одним из зарезервированных имён;
  • Имена переменных чувствительны к регистру (var name и var Name - две разные переменные);

В JavaScript также есть условность использования camelCase регистра в именах функций и переменных, объявленных с помощью var или let. Это практика заключается в том, что первое слово пишется строчными буквами, а затем заглавная первая буква каждого последующего слова без пробелов между ними (например let shippedOrderIndex). Имена постоянных переменных, объявленных с помощью ключевого слова const, обычно пишутся в верхнем регистре (например const ORDERS).

Разница между var, let и const

В JavaScript существует три разных ключевых слова для объявления переменной, что добавляет языку дополнительный уровень сложности. Различия между ними основаны на области видимости, поднятии и переопределении.

Ключевое слово Область видимости Поднятие Может быть переопределена Может быть повторно объявлена
var функциональная есть да да
let блочная нет да нет
const блочная нет нет нет

Наверняка, возникает вопрос, какие переменные из трех нужно использовать в своём коде. Общепринятая практика - как можно больше использовать const и let в случаях с циклами и переопределениями. Ну и вцелом, применение var лучше избегать (кроме работы с унаследованным кодом).

Область видимости переменной

Область видимости в JavaScript относится к текущему контексту кода, который определяет доступность переменной. Есть два типа области видимости: локальная и глобальная:

  • Глобальные переменные объявлены вне блока
  • Локальные переменные - это те, которые объявлены внутри блока

В приведенном ниже примере создадим глобальную переменную:

var color = 'red';

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

В приведенном ниже примере создадим глобальную переменную color. Внутри функции находится локальная переменная с тем же именем. Считывая их в консоли, мы можем увидеть, что значение переменной отличается в зависимости от области видимости:

// Объявим новую переменную в глобальной области видимости
var color = 'red';

function transform() {
  // Объявим новую переменную в локальной функциональной области видимости
  var color = 'blue';
  console.log(color);
}

// Выведем глобальную и локальную переменные
console.log(color);
transform();
console.log(color);

// В консоли получим:
// 'red'
// 'blue'
// 'red'

В этом примере локальная переменная ограничена функцией. Переменные, объявленные с помощью ключевого слова var, всегда имеют функциональную область видимости, что означает, что они распознают функцию, как некую сущность, имеющую отдельную область видимости. Следовательно, эта переменная, объявленная в локальной области видимости, недоступна в глобальной (т.е. вне функции).

Новые ключевые слова let и const имеют блочную область видимости. Это означает, что новая локальная область видимости создается из любого блока, включая функции, операторы условий, а также циклы.

Чтобы проиллюстрировать разницу между функциональными и блочными переменными, объявим новую переменную в блоке if с помощью let:

var lucky = true;

// Объявим глобальную переменную
let result = 'проигрыш';

if (lucky) {
  // Объявим переменную в блочной области видимости
  let result = 'выигрыш';
  console.log(`Я удачлив, меня ждёт ${result}.`);
}

console.log(`Я неудачлив, меня ждёт ${result}.`);

// В консоли получим:
// 'Я удачлив, меня ждёт выигрыш.'
// 'Я неудачлив, меня ждёт проигрыш.'

В этом примере переменная result имеет одно значение глобально ("проигрыш") и другое значение локально ("выигрыш"). Однако, если бы мы использовали var, результат был бы другим:

var lucky = true;

// Объявим глобальную переменную
var result = 'проигрыш';

if (lucky) {
  // Объявим переменную в блочной области видимости
  var result = 'выигрыш';
  console.log(`Я удачлив, меня ждёт ${result}.`);
}

console.log(`Я неудачлив, меня ждёт ${result}.`);

// В консоли получим:
// 'Я удачлив, меня ждёт выигрыш.'
// 'Я неудачлив, меня ждёт выигрыш.'

В результате этого примера и глобальная переменная, и переменная с блочной областью видимости имеют одно и то же значение - "выигрыш". Это связано с тем, что вместо создания новой локальной переменной с помощью var, мы просто переопределили уже имеющуюся переменную в той же области. Для var блочная область видимости (представленная в нашем примере контейнером с условием if) - это глобальная. Поэтому обычно рекомендуется объявлять переменные с блочной областью видимости, поскольку они создают код, который с меньшей вероятностью может переопределить их же значения.

Хоистинг (поднятие)

До сих пор в большинстве примеров мы использовали var для объявления переменной и инициализировали ее значением. После объявления и инициализации мы можем получить доступ к переменной или переопределить её.

Если мы попытаемся использовать переменную до того, как она будет объявлена ​​и инициализирована, она вернет undefined:

// Попытаемся использовать переменную до её объявления
console.log(x);

// Объявление переменной с присвоением значения
var x = 100;

// В консоли получим:
// undefined

Однако, если мы не применим ключевое слово var, мы уже не объявим переменную, а только инициализируем ее. Скрипт вернет ReferenceError и остановит выполнение:

// Попытаемся использовать переменную до её объявления
console.log(x);

// Присвоим значение переменной без её объявления
x = 100;

// В консоли получим:
// ReferenceError: x is not defined

Причина такого поведения как раз связана с поднятием (хоистингом) - поведением JavaScript, в котором объявления переменных и функций перемещаются в верхнюю часть своей области видимости. Поскольку поднимается только фактическое объявление, а не инициализация, значение в первом примере возвращает undefined.

Чтобы более наглядно продемонстрировать эту концепцию, ниже приведен код, который мы написали, и то, как JavaScript на самом деле его интерпретировал:

// Код, который мы написали
console.log(x);
var x = 100;

// Как JavaScript его считывает
var x;  // строка (1)
console.log(x);  // строка (2)
x = 100;  // строка (3)

Что произошло. JavaScript сохранил x в памяти как переменную перед выполнением скрипта (строка 1). Поскольку он вызывался до того, как был определен, значение x будет undefined, а не 100 (строка 2). Однако скрипт не вызовет ошибку ReferenceError и не остановится. И хотя ключевое слово var на самом деле не изменило местоположение переменной, этот пример даёт полезное представление о том, как работает хоистинг. Однако такое поведение может вызвать проблемы, потому что программист, написавший этот код, скорее всего, ожидает, что значение x будет true, хотя вместо этого он undefined.

Мы также можем увидеть, как хоистинг может привести к непредсказуемым результатам в следующем примере:

// Инициализируем глобальную переменную
var x = 100;

function hoist() {
  // Условие, которое не должно повлиять на результат кода
  if (false) {
    var x = 200;
  }
  console.log(x);
}

hoist();

// В консоли получим:
// undefined

В этом примере мы объявили x глобально равным 100. В зависимости от оператора if x может измениться на 200, но, поскольку условие было ложным, оно не должно было влиять на значение x. Вместо этого x был поднят в начало функции hoist (), и значение стало undefined.

Этот тип непредсказуемого поведения потенциально может вызвать ошибки в программе. Поскольку let и const имеют блочную область видимости, они не будут подниматься подобным образом.

// Инициализируем глобальную переменную
let x = true;

function hoist() {
  // Объявляем x в области видимости функции
  if (3 === 4) {
    let x = false;
  }
  console.log(x);
}

hoist();

// В консоли получим:
// true

Повторное объявление переменных, которое возможно с помощью var, вызовет ошибку с let и const.

// Повторно объявим переменную, объявленную с помощью var
var x = 1;
var x = 2;

console.log(x);

// В консоли получим:
// 2
// Повторно объявим переменную, объявленную с помощью let
let y = 1;
let y = 2;

console.log(y);

// В консоли получим:
// Uncaught SyntaxError: Identifier 'y' has already been declared

Подводя итог, можно сказать, что переменные, введенные с помощью var, потенциально могут быть затронуты хоистингом, механизмом в JavaScript, в котором объявления переменных сохраняются в памяти. Это может привести к undefined-переменным в коде. Введение let и const решает эту проблему, вызывая ошибку при попытке использовать переменную перед ее объявлением или при попытке объявить переменную более одного раза.

Константы

Многие языки программирования содержат константы, которые представляют собой значения, которые нельзя изменить. В JavaScript идентификатор константы моделируется после ключевого слова const, и значения, присвоенные константе, не могут быть переопределены.

Обычно все константные идентификаторы пишутся в верхнем регистре. Это делает их легко отличимыми от других значений переменных.

В приведенном ниже примере объявим переменную COLOR как константу с ключевым словом const. Попытка переопределить переменную приведет к ошибке.

// Объявим переменную с помощью const
const COLOR = 'red';

// Попробуем её переопределить
COLOR = 'green';

console.log(COLOR);

// В консоли получим:
// Uncaught TypeError: Assignment to constant variable.

Поскольку значения const нельзя переопределить, их нужно объявлять и инициализировать одновременно, иначе возникнет ошибка.

// Объявим переменную, но не инициализируем
const COLOR;

console.log(COLOR);

// В консоли получим:
// Uncaught SyntaxError: Missing initializer in const declaration

Значения, которые нельзя изменить при программировании, называют иммутабельными, в то время как значения, которые можно изменить, называют мутабельными. Хотя значения const нельзя переопределить, они считаются мутабельными, поскольку можно изменять свойства объектов, объявленных с помощью const.

// Создадим объект CAR, содержащий 2 свойства:
const CAR = {
  color: 'blue',
  price: 15000
}

// Изменим значение одного из свойств CAR:
CAR.price = 20000;

console.log(CAR);

// В консоли получим:
// { color: 'blue', price: 20000 }

Константы полезны для того, чтобы дать понять вам и другим программистам, работающим над проектом вместе с вами, что предполагаемая переменная не должна переопределяться. Если переменная может быть изменена в будущем, то, вероятно, лучше использовать let для объявления переменной.