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

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

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

Оглавление

  • неопределенный и ReferenceError
  • Поднятие переменных
  • ES5
  • ES6
  • Поднятие классов
  • Заключение

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

Если вы когда-нибудь задумывались, почему вы можете вызывать функции до того, как написали их в своем коде, читайте дальше!

# undefined против ReferenceError

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

console.log(typeof variable); // Output: undefined

Это подводит нас к нашему первому замечанию:

В JavaScript необъявленной переменной при выполнении присваивается значение undefined и она также имеет тип undefined.

Наш второй пункт:

console.log(variable); // Output: ReferenceError: variable is not defined

В JavaScript возникает ошибка ReferenceError при попытке доступа к ранее необъявленной переменной.

Поведение JavaScript при обработке переменных становится более нюансированным из-за подъема. Мы рассмотрим это подробно в следующих разделах.

# Подъем переменных

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

Однако, поскольку JavaScript позволяет нам одновременно объявлять и инициализировать наши переменные, это наиболее часто используемый шаблон:

var a = 100;

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

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

Чтобы продемонстрировать это поведение, взгляните на следующее:

function hoist() {
  a = 20;
  var b = 100;
}
hoist();
console.log(a); 
/* 
Accessible as a global variable outside hoist() function
Output: 20
*/
console.log(b); 
/*
Since it was declared, it is confined to the hoist() function scope.
We can't print it out outside the confines of the hoist() function.
Output: ReferenceError: b is not defined
*/

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

#ES5

вар

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

Глобальные переменные

console.log(hoist); // Output: undefined

var hoist = 'The variable has been hoisted.';

Мы ожидали, что результат журнала будет: ReferenceError: hoist is not defined, но вместо этого он вывел undefined.

Почему это произошло?

Это открытие приближает нас к борьбе с добычей.

JavaScript поднял объявление переменной. Вот как приведенный выше код выглядит для интерпретатора:

var hoist;

console.log(hoist); // Output: undefined
hoist = 'The variable has been hoisted.';

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

Переменные области действия функции

Как мы видели выше, переменные в глобальной области видимости поднимаются на вершину области. Далее давайте посмотрим, как поднимаются переменные области видимости функции.

function hoist() {
  console.log(message);
  var message='Hoisting is all the rage!'
}

hoist();

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

Если вы догадались, undefined вы правы. Если вы этого не сделали, не волнуйтесь, мы скоро докопаемся до сути.

Вот как интерпретатор видит приведенный выше код:

function hoist() {
  var message;
  console.log(message);
  message='Hoisting is all the rage!'
}

hoist(); // Ouput: undefined

Объявление переменной var message, областью действия которой является функция hoist(), поднимается в начало функции.

Чтобы избежать этой ловушки, мы должны обязательно объявить и инициализировать переменную перед ее использованием:

function hoist() {
  var message='Hoisting is all the rage!'
  return (message);
}

hoist(); // Ouput: Hoisting is all the rage!

Строгий режим

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

Запускаем наш код в строгом режиме:

  1. Устраняет некоторые скрытые ошибки JavaScript, заменяя их явными ошибками броска, которые выдает интерпретатор.
  2. Исправлены ошибки, из-за которых движкам JavaScript было сложно выполнять оптимизацию.
  3. Запрещает некоторый синтаксис, который, вероятно, будет определен в будущих версиях JavaScript.

Мы включаем строгий режим, предваряя наш файл или функцию

'use strict';
// OR
"use strict";

Давайте проверим это.

'use strict';
console.log(hoist); // Output: ReferenceError: hoist is not defined
hoist = 'Hoisted';

Мы видим, что вместо того, чтобы предположить, что мы пропустили объявление нашей переменной, use strict остановил нас, явно выдав Reference error. Попробуйте без строгого использования и посмотрите, что произойдет.

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

#ES6

ECMAScript 6, ECMAScript 2015, также известный как ES6, является последней версией стандарта ECMAScript на момент написания этой статьи январь 2017 и вносит несколько изменений в es5.

Нас интересует, как изменения в стандарте влияют на объявление и инициализацию переменных JavaScript.

позволять

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

Начнем с изучения поведения ключевого слова let.

console.log(hoist); // Output: ReferenceError: hoist is not defined ...
let hoist = 'The variable has been hoisted.';

Как и прежде, для ключевого слова var мы ожидаем, что вывод журнала будет undefined. Однако, поскольку es6 let не любит, когда мы используем необъявленные переменные, интерпретатор явно выдает ошибку Reference.

Это гарантирует, что мы всегда сначала объявляем наши переменные.

Тем не менее, мы все еще должны быть осторожны здесь. Реализация, подобная следующей, приведет к выводу undefined вместо Reference error.

let hoist;
console.log(hoist); // Output: undefined
hoist = 'Hoisted'

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

константа

Ключевое слово const было введено в es6, чтобы разрешить неизменяемые переменные. То есть переменные, значение которых нельзя изменить после присвоения.

С const, как и с let, переменная поднимается наверх блока.

Давайте посмотрим, что произойдет, если мы попытаемся переназначить значение, прикрепленное к переменной const.

const PI = 3.142;
PI = 22/7; // Let's reassign the value of PI
console.log(PI); // Output: TypeError: Assignment to constant variable.

Как const изменяет объявление переменной? Давайте взглянем.

console.log(hoist); // Output: ReferenceError: hoist is not defined
const hoist = 'The variable has been hoisted.';

Как и в случае с ключевым словом let, интерпретатор спасает нас, вместо того чтобы молча завершать работу с undefined, явно бросая Reference error.

То же самое происходит при использовании const внутри функций.

function getCircumference(radius) {
  console.log(circumference)
  circumference = PI*radius*2;
  const PI = 22/7;
}
getCircumference(2) // ReferenceError: circumference is not defined

С const es6 идет дальше. Интерпретатор выдает ошибку, если мы используем константу перед ее объявлением и инициализацией.

Наш линтер также быстро сообщает нам об этом преступлении:

PI was used before it was declared, which is illegal for const variables.

Глобально,

const PI;
console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
PI=3.142;

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

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

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

Функции JavaScript можно приблизительно классифицировать следующим образом:

  1. Объявления функций
  2. Функциональные выражения

Мы исследуем, как оба типа функций влияют на подъем.

Объявления функций

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

hoisted(); // Output: "This function has been hoisted."
function hoisted() {
  console.log('This function has been hoisted.');
};

Функциональные выражения

Однако функциональные выражения не поднимаются.

expression(); //Output: "TypeError: expression is not a function
var expression = function() {
  console.log('Will this work?');
};

Давайте попробуем комбинацию объявления функции и выражения.

expression(); // Ouput: TypeError: expression is not a function
var expression = function hoisting() {
  console.log('Will this work?');
};

Как мы видим выше, объявление переменной поднимается, а присвоение функции — нет. Поэтому интерпретатор выдает TypeError, так как он видит expression как переменную, а не как функцию.

# Классы подъема

Классы JavaScript также можно свободно классифицировать как:

  1. Объявления классов
  2. Выражения класса

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

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

var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: ReferenceError: Hobbit is not defined

class Hobbit {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
  }
}

Я уверен, вы заметили, что вместо undefined мы получаем Reference error. Это свидетельство подкрепляет нашу позицию о том, что объявления классов поднимаются.

Если вы обращаете внимание на свой линтер, он дает нам полезный совет.

Hobbit was used before it is declared, which is illegal for class variables

Итак, что касается объявлений классов, чтобы получить доступ к объявлению класса, вы должны сначала объявить.

class Hobbit {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
  }
}

var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: { height: 100, weight: 300 }

Выражения класса

Как и их аналоги-функции, выражения класса не поднимаются.

Вот пример с безымянным или анонимным вариантом выражения класса.

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor

var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Вот пример с именованным выражением класса.

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor


var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Правильный способ сделать это так:

var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square);

# Вывод

Давайте резюмируем то, что мы видели до сих пор:

  1. При использовании es5 var попытка использовать необъявленные переменные приведет к тому, что при подъеме переменной будет присвоено значение undefined.
  2. При использовании es6 let и const использование необъявленных переменных приведет к ошибке ссылки, поскольку переменная остается неинициализированной при выполнении.

Следовательно,

  1. Мы должны сделать привычкой объявлять и инициализировать переменные JavaScript перед использованием.
  2. Использование строгого режима в JavaScript es5 может помочь выявить необъявленные переменные.

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