DevSurge 💦

В чем разница между var, let и const

Cover Image for В чем разница между var, let и const
Mark Nelyubin
Mark Nelyubin

Стандарт ES6 (ECMAScript 2015) предложил множество новых возможностей. Одна из них, block scope declarations: let и const.

Декларации var, let и const позволяют присвоить и хранить значение. Но между ними есть разница. В статье расскажу про различия этих декларации с точки зрения их области видимости (scope), использования и подъема (hoisting).

Область видимости

Область видимости — часть кода, в пределах которой доступен идентификатор имени некоторой программной сущности (переменной или функции).

Рассмотрим пример ниже, чтобы базово понять тему области видимости:

var global = "value";

function sumToTen(a, b) {
  var c = 10;
  return a + b + c;
}

if (global.length) {
  var response = "not empty";
}
  • global — идентификатор имени переменной, объявлен и доступен в глобальной области видимости. Глобальная — значит на любом уровне (глобальном, функциональном и блочном);
  • sumToTen — идентификатор имени функции, объявлен и доступен глобально;
  • a, b — параметры, c — идентификатор имени переменной. Объявлены и доступны в рамках функциональной области видимости, то есть внутри тела функции {}
  • response — идентификатор имени переменной, объявлен внутри блочной области видимости, а доступен глобально (об этом позже).

Декларация var объявляет переменную в рамках глобальной или функциональной области видимости, а let и const — в рамках блочной области видимости.

Чтобы показать, как работает var, использую немного переработанный пример выше:

var global = "value";
console.log(global); // value

function logTen() {
  console.log(global); // value
  var c = 10;
  return c;
}
console.log(c); // error: c is not defined

if (global.length) {
  console.log(global); // value
  var response = "not empty";
  console.log(response); // not empty
}

console.log(response); // not empty
  • переменная global объявлена глобальна и доступна везде — глобально, в теле функции, внутри блока
  • переменная c объявлена в функциональной области видимости и доступна только там
  • переменная response объявлена в блочной области видимости, но доступна везде, то есть глобально

Из-за отсутствия привязки к блочной области видимости работать с var не очень удобно, приходится придумывать новые наименования:

var response = 200;
if (true) {
  /* объявили в блоке, но вместо создания новой переменной перезаписалась старая */
  var response = 404; 
}
console.log(response); // 404

C let и const такой проблемы не будет. Они объявляются внутри блока и доступны только внутри блока:

if (true) {
  let greeter = "Приветствуем вас!";
}
console.log(greeter); // error: greeter is not defined

Если объявим новую переменную внутри блока, она не перезапишет глобальную переменную с тем же именем и будет храниться отдельно внутри блока:

//оба экземпляра рассматриваются как разные переменные, т.к. у них разные области видимости
let response = 200;
if (true) {
  let response = 404; 
}
console.log(response); // 200

Объявление и обновление (declaration&update)

  • var можем объявить, переобъявить, перезаписать
  • let можем объявить и перезаписать, но не можем переобъявить
  • const можем объявить, но не можем переобъявить или перезаписать

Начнем с var:

var greeter = "привет";
// переобъявили
var greeter = "приветики-пистолетики";
console.log(greeter); // "приветики-пистолетики"
// обновили:
greeter = "доброе утро коллеги";
console.log(greeter); // "доброе утро коллеги"

Рассмотрим let:

// объявили
let letGreeter = "привет";
// при попытке переобновить получим ошиьку
let letGreeter = "приветики-пистолетики"; //  SyntaxError: Identifier 'letGreeter' has already been declared
// обновили:
letGreeter = "приветики-пистолетики";
console.log(letGreeter); // "приветики-пистолетики"

const самый строгий, так как подразумевает хранение константы:

// объявили
const cGreeter = "привет";
// переобъявить не можем
const cGreeter = "приветики-пистолетики"; // SyntaxError: Identifier 'cGreeter' has already been declared
// перезаписать не можем
cGreeter = "приветики-пистолетики"; // error: Assignment to constant variable

console.log(cGreeter); // привет

Хотя мы не можем изменить значения примитивов в константе (numbers, strings, booleans, undefined, null), нужно помнить, что в случае с объектами хранится не само значение, а ссылка на объект, тогда как содержание объекта может измениться:

const user = {
  name: "Вася",
  age: 30,
};
user.name = "Петя";

console.log(user); // { name: 'Петя', age: 30 }

const numbers = [1, 2, 3];
numbers.push(4);
console.log(numbers); // [ 1, 2, 3, 4 ]

// а вот так делать нельзя:
numbers = [7, 9, 15] // error: Assignment to constant variable.

Подъем (hoisting)

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

Рассмотрим следующий пример:

console.log(capitalOfRussia); // undefined
var capitalOfRussia = "Москва";
console.log(capitalOfUkraine); // error: Cannot access 'capitalOfUkraine' before initialization
let capitalOfUkraine = "Киев";
console.log(capitalOfBelarus); // error: Cannot access 'capitalOfBelarus' before initialization
const capitalOfBelarus = "Минск";

Почему в случае с var мы получили значение undefined, а let и const выдали ошибку?

Переменные, объявление с помощью var поднимаются на вершину своей области видимости, инициализируются без присвоения значения (со значением undefined).

Интерпретатор преобразует код следующим образом:

var capitalOfRussia;
console.log(capitalOfRussia); // capitalOfRussia is undefined
var capitalOfRussia = "Москва";

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

// console.log(capitalOfRussia); // error: Cannot access 'capitalOfUkraine' before initialization
// Объявление переменной и присовение значения:
const capitalOfRussia = "Москва";
// Здесь переменная доступна и возвращает значение: 
console.log(capitalOfRussia); // "Москва"

Рассмотрим такой пример:

console.log(hello);

var hello = 'world';

function hello() {
  console.log('Hello there!');
}

Что появится в консоли по итогам исполнения этого кода?

Как я писал в начале этого раздела, функции тоже перемещаются в верхнюю часть области видимости перед выполнением кода. После поднятия код будет выглядеть так:

// Объявление функции переместилось на самый верх
function hello() {
  console.log('Hello there!');
}
var hello; // Объявление переменной переместилось наверх 
console.log(hello); // Выводит тело функции hello()

hello = 'world'; // Присовение значения переменной осталось на месте
  • Когда код выполняется, сначала обрабатывается объявление функции, а затем переменной hello присваивается значение функции.
  • Хотя переменная hello объявлена ниже, ей не присвоено новое значение, поэтому она содержит функцию
  • Если бы мы присвоили новое значение переменной hello после того, как она была объявлена в качестве функции, то значение бы перезаписалось и hello больше не ссылалась бы на функцию.

Таким образом, в консоль выведется hello() {console.log('Hello there!')}

Объявление без значения

В то время как var и let могут быть объявлены без значения, для const во время объявления нужно присвоить значение.

var fistName;
let secondName;
const thirdName; // SyntaxError: Missing initializer in const declaration
const fourthName = 'Peter G';

Итого

  • var декларации находятся в глобальной или функциональной области видимости, тогда как let и const в блочной.
  • var, let, const объявляют переменные. var также может быть переобъявлена и перезаписана, let — только перезаписана, const — ничего из перечисленного, только объявление.
  • Все они подняты на вершину своей области видимости. Но если var переменные инициализированы со значением undefined, то let и const не инициализированы.
  • По моей выборке из более чем десятка проектов, на состояние 2022 года в 90% юзкейсов используется const, в 10% — let (например, когда объявляем индекс, чтобы пройтись по массиву или какой-то счетчик), var видел только в legacy-коде и библиотеках.

Другие материалы

Cover Image for TypeScript Utility Types — вспомогательные типы и области их применения

TypeScript Utility Types — вспомогательные типы и области их применения

что такое Utility Types в TypeScript, расскажу про основные вспомогательные типы, и покажу, как применять их на реальных проектах.

Mark Nelyubin
Mark Nelyubin
Cover Image for Принципы SOLID с примерами на JS и Vue

Принципы SOLID с примерами на JS и Vue

Расскажу про принципы SOLID с актуальными примерами на JavaScript, Vue, React.

Mark Nelyubin
Mark Nelyubin