Стоят две банки: прозрачная с лимонным джемом и непрозрачная с  мёдом. На заднем плане открытое окошко операционной системы с пчёлкой
Иллюстрация: Кира Кустова

Переменные const, let и var

Чем отличаются const, let или var? Рассказываем, что такое переменные и какими они бывают.

Время чтения: 9 мин

Кратко

Скопировано

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

Что такое переменные

Скопировано

Переменные — это именованные контейнеры для хранения данных.

Для создания переменной используется ключевое слово let, const или var. Сразу за ключевым словом идёт название переменной либо перечень переменных через запятую. Создание переменной также называют объявлением переменной. Например:

        
          
          var singleVariablelet firstVariable, secondVariable, thirdVariable
          var singleVariable

let firstVariable, secondVariable, thirdVariable

        
        
          
        
      

Чаще всего, при объявлении переменной ей устанавливают стартовое значение при помощи оператора присваивания =. Тип значения может быть абсолютно любым — строка, число, объект, массив и так далее.

        
          
          // Объявление переменной и присваиваниеlet string = 'foo'const array = ['foo', 'bar', 'baz']var number = 10// Множественное объявление и присваиваниеlet firstNumber = 5,  secondNumber = 10
          // Объявление переменной и присваивание
let string = 'foo'
const array = ['foo', 'bar', 'baz']
var number = 10

// Множественное объявление и присваивание
let firstNumber = 5,
  secondNumber = 10

        
        
          
        
      

Затем переменную можно использовать как заменитель значения в коде:

        
          
          let name = 'Уолтер'let surname = 'Вайт'let fullName = name + ' ' + surnameconsole.log(fullName)// Уолтер Вайт
          let name = 'Уолтер'
let surname = 'Вайт'
let fullName = name + ' ' + surname

console.log(fullName)
// Уолтер Вайт

        
        
          
        
      

Правила именования переменных

Скопировано

Для имени переменной можно использовать следующие символы:

  • буквы латинского алфавита;
  • цифры;
  • символы $ и _.

Первый символ не должен быть цифрой:

        
          
          let letters, $dollarsign, _underscorelet 1number// SyntaxError: Invalid or unexpected token
          let letters, $dollarsign, _underscore

let 1number
// SyntaxError: Invalid or unexpected token

        
        
          
        
      

В качестве названий переменных нельзя использовать зарезервированные языком слова. Например: class, super, throw, yield, var, let, const и так далее. С полным списком таких слов можно ознакомиться здесь.

Создание переменных

Скопировано

Перед выполнением скрипта JavaScript находит код создания переменных и заранее создаёт их. Получается, что в начале выполнения скрипта все переменные, описанные в коде, уже объявлены. В зависимости от браузера, они могут быть равны undefined (в Chrome и Safari), либо, в случае с let и const в браузере Firefox, не равны ничему и иметь специальное состояние uninitialized:

        
          
          console.log('Старт')var byVar = 5let byLet = 10const byConst = 15console.log('Конец')
          console.log('Старт')

var byVar = 5
let byLet = 10
const byConst = 15

console.log('Конец')

        
        
          
        
      
Дебаггер Firefox, выполнение только началось, и все переменные уже объявлены, хотя код ещё не дошёл до выполнения нужных строк.
Код ещё не начал выполняться, а переменные уже объявлены.

В конце скрипта, после того как произошло присвоение стартовых значений, переменные равны 5, 10 и 15:

Дебаггер Firefox, выполнение дошло до строк с объявлением переменных и значения установлены.
JavaScript выполнил код объявления переменных и установил значения.

Получается, что некоторое время переменная может содержать значение undefined и быть доступной для чтения. Этим нужно пользоваться с осторожностью.

Переменные let и const

Скопировано

Переменные let и const появились в версии EcmaScript 2015 года (ES6), и сейчас используются намного чаще чем var.

Объявление

Скопировано

Используя ключевое слово let, можно объявить переменную без присвоения ей начального значения. В таком случае она будет равна undefined:

        
          
          let aconsole.log(a)// undefineda = 5console.log(a)// 5
          let a
console.log(a)
// undefined

a = 5
console.log(a)
// 5

        
        
          
        
      

При помощи const нельзя объявлять переменные без значения:

        
          
          const a// SyntaxError: Missing initializer in const declaration// Правильноconst b = 5
          const a
// SyntaxError: Missing initializer in const declaration

// Правильно
const b = 5

        
        
          
        
      

К переменным let и const нельзя обращаться до их объявления в коде:

        
          
          console.log(a)// ReferenceError: Cannot access 'a' before initializationconsole.log(b)// ReferenceError: Cannot access 'b' before initializationlet a = 5const a = 5
          console.log(a)
// ReferenceError: Cannot access 'a' before initialization
console.log(b)
// ReferenceError: Cannot access 'b' before initialization

let a = 5
const a = 5

        
        
          
        
      
Почему так?

У let и const есть так называемая temporal dead zone (TDZ) — момент выполнения скрипта до объявления переменной. Переменная может использоваться и выше объявления, при условии, что содержащая её часть кода будет выполнена после инициализации:

        
          
          function foo() {  console.log('from foo', a)}Promise.resolve()  .then(() => console.log('from promise', a))setTimeout(() => console.log('from timer',a))let a = 10foo()// 'from foo 10', 'from promise 10', 'from timer 10'
          function foo() {
  console.log('from foo', a)
}

Promise.resolve()
  .then(() => console.log('from promise', a))

setTimeout(() => console.log('from timer',a))

let a = 10

foo()

// 'from foo 10', 'from promise 10', 'from timer 10'

        
        
          
        
      

TDZ есть также и у ES6-классов, несмотря на то, что они являются «синтаксическим сахаром» над обычными функциями.

        
          
          console.log(Foo)class Foo {  constructor(bar) {    this.bar = bar  }}// ReferenceError: Cannot access 'Foo' before initialization
          console.log(Foo)

class Foo {
  constructor(bar) {
    this.bar = bar
  }
}
// ReferenceError: Cannot access 'Foo' before initialization

        
        
          
        
      

А функции (объявленные как Function Declaration) TDZ не имеют.

        
          
          console.log(Foo)function Foo() {  this.bar = bar}// ƒ Foo() { this.bar = bar}
          console.log(Foo)

function Foo() {
  this.bar = bar
}
// ƒ Foo() { this.bar = bar}

        
        
          
        
      

Оба типа переменных имеют блочную область видимости и не становятся частью глобального объекта (window в браузере, global в Node.js). Блочная область видимости не даёт получить значение переменной вне блока, где она была объявлена.

Если объявить переменные внутри блока if, то обращение к ним вне блока будет выбрасывать ошибку:

        
          
          if (true) {  let a = 5  const b = 10  console.log(a)  // 5  console.log(b)  // 10}console.log(a)// ReferenceError: a is not definedconsole.log(b)// ReferenceError: b is not defined
          if (true) {
  let a = 5
  const b = 10

  console.log(a)
  // 5
  console.log(b)
  // 10
}

console.log(a)
// ReferenceError: a is not defined

console.log(b)
// ReferenceError: b is not defined

        
        
          
        
      

Одинаковые имена переменных

Скопировано

Объявление переменной с именем, которое уже используется в текущей области видимости, приведёт к ошибке:

        
          
          let a = 5let a = 10// SyntaxError: Identifier 'a' has already been declared
          let a = 5

let a = 10
// SyntaxError: Identifier 'a' has already been declared

        
        
          
        
      

То же правило работает и при использовании const, и при использовании смешанного подхода:

        
          
          const a = 5const a = 10// SyntaxError: Identifier 'a' has already been declaredvar b = 5const b = 10// SyntaxError: Identifier 'b' has already been declared
          const a = 5
const a = 10
// SyntaxError: Identifier 'a' has already been declared

var b = 5
const b = 10
// SyntaxError: Identifier 'b' has already been declared

        
        
          
        
      

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

        
          
          let name = 'Ольга'if (true) {  let name = 'Елена'  console.log(name)  // Елена}console.log(name)// Ольга
          let name = 'Ольга'

if (true) {
  let name = 'Елена'
  console.log(name)
  // Елена
}

console.log(name)
// Ольга

        
        
          
        
      

Смена значения в let и const

Скопировано

Значение в переменной, созданной через let, можно изменять:

        
          
          let a = 5console.log(a)// 5a = 10console.log(a)// 10
          let a = 5
console.log(a)
// 5

a = 10
console.log(a)
// 10

        
        
          
        
      

Стартовое значение const изменить нельзя, будь то примитивное значение:

        
          
          const a = 5a = 10// TypeError: Assignment to constant variable
          const a = 5
a = 10
// TypeError: Assignment to constant variable

        
        
          
        
      

Или ссылка на объект:

        
          
          const obj = {  a: 5,}obj = {  a: 10,}// TypeError: Assignment to constant variable
          const obj = {
  a: 5,
}

obj = {
  a: 10,
}
// TypeError: Assignment to constant variable

        
        
          
        
      

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

        
          
          const obj = {  a: 5,}obj.a = 10console.log(obj)// { a: 10 }
          const obj = {
  a: 5,
}

obj.a = 10

console.log(obj)
// { a: 10 }

        
        
          
        
      

Переменные var

Скопировано

Объявление переменных при помощи ключевого слова var было в JavaScript с первых версий.

Объявление

Скопировано

Переменные var можно объявлять без присвоения им значения, в таком случае они будут равны undefined:

        
          
          var aconsole.log(a)// undefinedvar b = 5console.log(b)// 5
          var a
console.log(a)
// undefined

var b = 5
console.log(b)
// 5

        
        
          
        
      

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

        
          
          if (true) {  var a = 5}function foo() {  var b = 10}console.log(a)// 5console.log(b)// ReferenceError: b is not defined
          if (true) {
  var a = 5
}

function foo() {
  var b = 10
}

console.log(a)
// 5
console.log(b)
// ReferenceError: b is not defined

        
        
          
        
      

Объявление переменных вне функций делает их глобальными переменными. Они доступны как свойства глобального объекта:

        
          
          var varVariable = 5console.log(window.varVariable)// 5
          var varVariable = 5

console.log(window.varVariable)
// 5

        
        
          
        
      

К переменным, объявленным при помощи ключевого слова var, можно обращаться до момента объявления. В отличие от let и const, ошибки это не вызовет. Такое поведение называется hoisting - «всплытие»:

        
          
          console.log(a)// undefinedvar a = 5console.log(a)// 5
          console.log(a)
// undefined

var a = 5

console.log(a)
// 5

        
        
          
        
      

Разберём, как работает функциональная область видимости:

        
          
          var a = 5function foo() {  console.log(a)  // undefined  var a = 10  console.log(a)  // 10}foo()console.log(a)// 5
          var a = 5

function foo() {
  console.log(a)
  // undefined

  var a = 10
  console.log(a)
  // 10
}

foo()

console.log(a)
// 5

        
        
          
        
      

Перед выполнением функции в глобальной области видимости присутствует переменная a, равная 5:

Старт выполнения кода в дебаггере. Уже объявлена переменная "а".
Перед выполнением функции переменная уже инициализирована в глобальной области видимости.

Во время выполнения функции формируется новая функциональная область видимости, в которой тоже присутствует переменная a. Эта переменная была объявлена с помощью var внутри функции, в момент выполнения которой она «всплыла» со значением равным undefined. В строке 4 происходит обращение именно к ней (до её объявления), а не к той, что находится вне функции.

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

В строке 8 значение переменной a уже равно 10.

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

После выполнения функции локальная область видимости была удалена. В консоли выводится глобальная переменная a.

Дебаггер, JavaScript выполнил весь код. Локальная переменная уничтожена, доступна только глобальная.
На последнем шаге локальная переменная уже недоступна, так как выполняется код из глобальной области видимости.

Более подробно об этом можно прочитать в отдельной статье

Смена значения в var

Скопировано

Значение, хранящееся в переменной var, можно изменить двумя способами:

  • обратиться к имени переменной и присвоить новое значение:
        
          
          var a = 5console.log(a)// 5a = 10console.log(a)// 10
          var a = 5
console.log(a)
// 5

a = 10
console.log(a)
// 10

        
        
          
        
      
  • обратиться к имени переменной вместе с ключевым словом var:
        
          
          var a = 5console.log(a)// 5var a = 10console.log(a)// 10
          var a = 5
console.log(a)
// 5

var a = 10
console.log(a)
// 10

        
        
          
        
      

На практике

Скопировано

Сергей Фомин советует

Скопировано

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

🛠 Называйте переменные так, чтобы можно было легко понять, что в них хранится. Например:

        
          
          let url = 'https://doka.guide'const now = Date.now()const user = {  name: 'John',  age: 30,}
          let url = 'https://doka.guide'

const now = Date.now()

const user = {
  name: 'John',
  age: 30,
}

        
        
          
        
      

Исключением считается именование счётчиков в циклах for, в которых обычно используются одиночные буквы i, j, и так далее.

Имена переменных могут состоять из нескольких слов, поэтому для удобства их чтения в JavaScript принято использовать так называемую «верблюжью нотацию» (camelCase), когда каждое новое слово, начиная со второго, пишется с заглавной буквы:

        
          
          const fullName = 'John Doe'const arrayOfNumbers = [1, 2, 3]
          const fullName = 'John Doe'

const arrayOfNumbers = [1, 2, 3]

        
        
          
        
      

Имена констант (переменные, которые не меняют своё значение) принято писать, используя screaming_snake_case. В данной нотации все слова пишутся заглавными буквами, а разделителем является символ _.

        
          
          const BASE_URL = 'https://doka.guide'const PORT = 3000const UNAUTHORIZED_CODE = 401
          const BASE_URL = 'https://doka.guide'

const PORT = 3000

const UNAUTHORIZED_CODE = 401

        
        
          
        
      

На собеседовании

Скопировано
Задать вопрос в рубрику
🤚 Я знаю ответ

Скопировано

null задаётся переменной явно и означает, что она является объектом, но структура этого объекта ещё не определена. undefined присваивается переменной (переменная не декларирует объект), когда она была объявлена, но не было определено её начальное значение. Функция может возвращать undefined или null. Всё зависит от того, что мы ожидаем в результате работы функции. Если мы ожидаем объект, но по каким-то причинам функция его вернуть не может, то возвращаем null. Если функция должна вернуть, например, число (главное, не объект), но не может этого сделать, то она возвращает undefined.

Без начального значения можно оставлять только переменную, объявленную через let или var. Если объявить переменную через const и не задать ей начального значения, будет ошибка: Uncaught SyntaxError: Missing initializer in const declaration.

Поговорим немного о приведении типов. Для начала, пример:

        
          
          console.log(null + null); // 0console.log(undefined + undefined); // NaN
          console.log(null + null); // 0
console.log(undefined + undefined); // NaN

        
        
          
        
      

Почему так?

По спецификации EcmaScript

  • null во время сложения приводится к нулю;
  • undefined во время сложения приводится к NaN. NaN это аббревиатура от "not a number" — не число. Результат арифметической операции равен NaN, если во время операции произошла ошибка и ожидаемый числовой результат не может быть выражен числом.

Есть ещё один хитрый пример:

        
          
          console.log(null + []); // "null"
          console.log(null + []); // "null"

        
        
          
        
      

Почему так?

Подсказка, почему так, кроется именно в типе результате: "null" — строка. А не примитивное значение null.

JavaScript сначала приводит массив к примитивному значению. Для этого вызывается метод toString(), который вызывает метод join(). Т.к. массив пустой, то join() вернёт пустую строку "". А сложение чего-то со строкой в JS возвращает строку. Поэтому null уже никуда не приводится, а возращается строка "null".

Немного упомяну и про оператор нулевого слияния (??). В выражении между двумя операндами он будет возвращать первый операнд, если он не равен null или undefined. Можно сказать, что ?? приравнивает смысл undefined и null к «ничего не содержит», и, в этом случае, кладёт в переменную значение второго операнда.

🤚 Я знаю ответ

Скопировано

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

        
          
          console.log(a);  // Не может получить доступ к 'a' до её инициализацииconsole.log(b);  // Не может получить доступ к 'b' до её инициализацииconsole.log(c);  // undefinedlet a = 10;const b = 20;var c = 30;
          console.log(a);  // Не может получить доступ к 'a' до её инициализации
console.log(b);  // Не может получить доступ к 'b' до её инициализации
console.log(c);  // undefined

let a = 10;
const b = 20;
var c = 30;

        
        
          
        
      

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

        
          
          if (true) {  let a = 10;  const b = 20;  var c = 30;}console.log(a); // ReferenceErrorconsole.log(b); // ReferenceErrorconsole.log(c); // 30
          if (true) {
  let a = 10;
  const b = 20;
  var c = 30;
}

console.log(a); // ReferenceError
console.log(b); // ReferenceError
console.log(c); // 30

        
        
          
        
      

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

        
          
          let a = 10;const b = 20;a = 15 // Все впорядкеb = 40 // TypeError
          let a = 10;
const b = 20;

a = 15 // Все впорядке
b = 40 // TypeError