В чем разница между var, let и const
Стандарт 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-коде и библиотеках.