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

Чтобы по-настоящему понять разницу между var, let и const, нам необходимо разобраться в следующих понятиях:

  • Объявление переменной;
  • Инициализация переменной;
  • Область видимости;
  • Механизм hoisting («поднятие»).

Объявление переменной

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

var foo; // объявление переменной
console.log(foo); // logs →undefined

Инициализация переменной

Инициализация переменной — это процесс присваивания значения переменной. Поэтому, когда мы определяем связь между ключевым словом var, интерпретатор присваивает значение undefined.

var foo; // объявление переменной
console.log(foo); // logs →undefined
foo = “something”; // Initialization
console.log(foo); // logs →something

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

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

Существует 2 типа области видимости:

  • Функциональная область видимости;
  • Блочная область видимости.

Функциональная область видимости

Переменные, объявленные внутри функции, входят в область видимости этой функции, а также в область видимости вложенных в нее функций, независимо от блоков.

function foo() {
 if(true) {
 var v = “var inside the if block”;
 console.log(v); //logs →var inside the if block
 } 
 v = “var outside the if block”;
 console.log(v); //logs →var outside the if block
}
foo();

Блочная область видимости

Переменные, объявленные внутри блока, входят в область видимости только этого блока, а также вложенных в него блоков, но не за пределами блока, даже если это все одна функция. В блоки входят if…else и циклы.

function bar() {
 if(true) {
 let l = “let inside the if block”;
 console.log(l); //logs →let inside the if block
 }
console.log(l); // Uncaught Reference Error: l is not defined
}
bar();

Поднятие

В веб-документации MDN говорится:

«Поднятие — это термин, использование которого вы не найдете ни в одной нормативной документации JavaScript до спецификации языка ES2015. Предполагалось, что поднятие — это обобщенный способ видения того, как работает контекст исполнения (особенно создание и исполнение стадий) в JavaScript. Однако это понятие по началу может сбивать с толку.

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

console.log(foo); //logs →undefined 
// это не приводит к ошибке, однако значение →undefined;
//это происходит из-за поднятия
var foo = “something”; //Инициализация
console.log(foo); //logs →something

Как можно упростить оценку интерпретатора JS для кода выше:

var foo; // Поднятое объявление ‘foo’
console.log(foo); записывает значение →undefined;
foo = “something”;
console.log(foo); //записывает значение →something

var

Идентификатор var объявляет переменную, опционально присваивая ей значение. Любая переменная, объявленная идентификатором var, имеет функциональную область видимости и идентифицирует объявленные переменные с помощью ключевого слова var. Такие переменные поднятые, а их первоначальное значение — undefined.

console.log(foo); //logs →undefined
var foo;
//код выше не приводит к ошибке из-за поднятия;

let

Идентификатор let объявляет локальную переменную. Любая переменная, объявленная с помощью let, имеет блочную область видимости. Значения подняты и не инициализированы. 

let foo;
console.log(foo); // Uncaught Reference Error: l is not defined
// код выше приводит к ошибке, потому что значение переменной не инициализировано;

Связи let создаются сверху области видимости конкретного блока, содержащего определение, часто называемого поднятием. В отличие от переменных, объявленных с помощью var, которые начинаются со значения undefined, переменные let не инициализируются, пока им не присвоится значение. Обращение к переменной до инициализации вызовет Reference Error (ошибка использования несуществующего названия).

const

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

Следовательно, const должен быть всегда инициализирован при объявлении, иначе возникает ошибка.

const foo = “something”;
foo = “other thing”; // Uncaught TypeError: Assignment to constant variable. 
const bar; //Uncaught SyntaxError: Missing initializer in const declaration

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

const score = {visitors: 0, home: 0};
score.visitors = 1; // Можно
score = {visitors: 1, home: 1}; // Uncaught TypeError: Assignment to constant variable.
// Нельзя

Один последний факт:

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

function funFact() {
 isGloballyAvailable = true;
}
funFact();
console.log(isGloballyAvailable); // записывает true

Чтобы понять что происходит, нам нужно вернуться назад к понятию поднятия. Обычно при инициализации переменной в нашем коде интерпретатор ищет «поднятые переменные» и затем присваивает или переназначает значение переменной. Однако, когда интерпретатор не может найти переменную внутри функции, он ищет ее среди переменных родительской функции, пока не дойдет до глобальной области видимости.

В нашем случае интерпретатор не найдет ‘isGloballyAvailable’ даже в глобальной области видимости, поэтому он автоматически туда ее добавит.

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

Так, когда использовать var, let или const?

ES2015(ES6) представил let и const. Зачем JavaScript разработчикам их вводить? Может, чтобы исправить проблемы, возникающие с var, или для улучшения читабельность кода?

Одна главная проблема с var связана с тем, что он позволяет переназначать переменные в коде, из-за чего не выбрасываются ошибки. Это приводит к различным побочным эффектам в коде. 

Распространенное мнение, которое сходится с моим:

Всегда отдавайте предпочтение const, если значение, присвоенное переменной, не изменится. Это подскажет будущим разработчикам, что у переменной неизменное значение.

А let используйте, если значение в будущем изменится. 

Где бы был полезен var, я не представляю. 

Читайте также:


Перевод статьи Gemhar Rafi — var, let and const in JavaScript Decoded…

Предыдущая статьяVSCode. 27 расширений для JavaScript разработчика
Следующая статьяКак легко оптимизировать Jupyter Notebook. Часть 1