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

JavaScript — чрезвычайно гибкий язык, и он с радостью позволит вам объявить переменную практически в любом месте. Например, следующее немедленно вызываемое функциональное выражение (IIFE) объявляет три переменные, а затем отображает их с помощью диалогового окна предупреждения. В качестве примечания: вы никогда не должны использовать окна предупреждений, но мы пытаемся доказать здесь свою точку зрения.

(function() {
  var foo = 1;
  var bar = 2;
  var baz = 3;
  alert(foo + " " + bar + " " + baz);
})();

Это выглядит как нормальный код JavaScript. Как и ожидалось, он отображает строку "1 2 3". Теперь предположим, что оповещение перемещено, как показано ниже.

(function() {
  var foo = 1;
  alert(foo + " " + bar + " " + baz);
  var bar = 2;
  var baz = 3;
})();

Если кто-то действительно написал этот код, вероятно, это было сделано по ошибке. Ясно, что оповещение происходит до объявления bar и baz. Однако это вполне корректный JavaScript, который не генерирует исключений. Вместо этого предупреждение отображает "1 undefined undefined".

Основываясь на нашем предыдущем эксперименте, кажется, что вы можете ссылаться на переменные, которые еще не существуют. Теперь давайте возьмем тот же IIFE, но полностью удалим объявление baz, как показано ниже. Внезапно у нас есть ReferenceError, потому что baz не определено.

(function() {
  var foo = 1;
  alert(foo + " " + bar + " " + baz);
  var bar = 2;
})();

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

(function() {
  var foo;
  var bar;
  var baz;
  foo = 1;
  alert(foo + " " + bar + " " + baz);
  bar = 2;
  baz = 3;
})();

Теперь понятно, почему второй пример не сгенерировал исключение. После подъема bar и baz фактически объявляются перед оператором предупреждения, хотя и с неопределенными значениями. В третьем примере baz был полностью удален. Следовательно, поднимать было нечего, и оператор alert привел к исключению.

Функция подъема

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

foo();
function foo() {
  alert("Hello!");
}

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

foo();
var foo = function() {
  alert("Hello!");
};

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

Что такое переменный подъем?

Механизм JavaScript обрабатывает все объявления переменных с использованием «var», как если бы они были объявлены в верхней части функциональной области (если объявлены внутри функции) или глобальной области видимости (если объявлены вне функции) независимо от того, где происходит фактическое объявление. По сути, это «подъем».

Таким образом, переменные могут быть доступны до их объявления.

Посмотрим в действии…

// OUTPUT : undefinedconsole.log(shape);
var shape = "square";
// OUTPUT : "square"console.log(shape);

Если вы переходите с языков на основе C, вы ожидаете, что при первом вызове console.log будет выдана ошибка, поскольку переменная shape не была определена в эта точка. Но интерпретатор JavaScript смотрит вперед и «поднимает» все объявления переменных наверх, а инициализация остается на том же месте.

Вот что происходит за кулисами:

//declaration getting hoisted at the topvar shape;
// OUTPUT : undefinedconsole.log(shape);
shape = "square";
// OUTPUT : "square"console.log(shape);

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

function getShape(condition) {    // shape exists here with a value of undefined
// OUTPUT : undefined    console.log(shape);
if (condition) {        var shape = "square";        // some other code        return shape;    } else {        // shape exists here with a value of undefined        return false;    }}

В приведенном выше примере вы можете видеть, как объявление shape поднимается вверху функции getShape(). Это связано с тем, что блоки if/else не создают локальную область видимости, как в других языках. Локальная область видимости — это, по сути, область видимости функции в JavaScript. Таким образом, форма доступна везде за пределами блока if и внутри функции со значением «undefined».

Такое поведение JavaScript по умолчанию имеет свои преимущества и недостатки. Неполное понимание этого может привести к тонким, но опасным ошибкам в нашем коде.

Введите объявления на уровне блоков!

В ES6 представлены параметры области видимости на уровне блоков, чтобы предоставить разработчикам больший контроль и гибкость в отношении жизненного цикла переменной.

Объявления на уровне блока выполняются в блочных/лексических областях, которые создаются внутри блока «{}».

пусть декларации

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

Поместите свои объявления let вверху в блоке, чтобы они были доступны во всем этом блоке.

Например:

function getShape(condition) {// shape doesn't exist here
// console.log(shape); => ReferenceError: shape is not defined
if (condition) {        let shape = "square";        // some other code        return shape;    } else {        // shape doesn't exist here as well        return false;    }}

Обратите внимание, что shape существует только внутри блока if и выдает ошибку при доступе за его пределами вместо вывода undefined, как в нашем предыдущем случае с vardeclarations.

ПРИМЕЧАНИЕ. Если идентификатор уже определен внутри области с var, использование того же идентификатора в объявлении let внутри той же области вызовет ошибку.
Кроме того, ошибка не будет выдана, если Объявление let создает переменную с тем же именем, что и у переменной во внешней области видимости. (Этот случай аналогичен объявлениям const, о которых мы вскоре поговорим.)

Например:

var shape = "square";let shape = "rectangle";
// SyntaxError: Identifier 'shape' has already been declared

и:

var shape = "square";if (condition) {    // doesn't throw an error    let shape = "rectangle";    // more code }// No error

константные объявления

Синтаксис объявления аналогичен let и var , жизненный цикл такой же, как let. Но вы должны соблюдать некоторые правила.

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

Например:

// valid const shape = "triangle";
// syntax error: missing initializationconst color;
// TypeError: Assignment to constant variableshape = "square"

Однако свойства объекта можно изменить!

const shape = {    name: "triangle",    sides: 3}
// WORKSshape.name = "square";shape.sides = 4;
// SyntaxError: Invalid shorthand property initializershape = {    name: "hexagon",    sides: 6}

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

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

Источник: Freecodecamp, SitePoint, MDM, StackOverflow.