Пойдем в обход…

Если вы проверите typeof(null), он всегда будет возвращать «объект». Это ошибка, как признал сам Брендан. Из-за унаследованных причин это никогда не будет исправлено, и нам приходится с этим жить.

Точно так же еще одна вещь, которую мы должны принять как симпатичную причуду JavaScript, — это разница между «неопределенным» и «не определенным».

var a = 'Albus';
var b = null;
var c;

console.log(a); // Albus
console.log(b); // null
console.log(c); // undefined
console.log(d); // Reference Error ...not defined...

a объявляется и определяется как строка «Альбус».

b объявляется и определяется с нулевым значением.

c объявлен, но не определен.

d не объявлен и не определен.

«Undefined» — это заполнитель для переменных, которые объявлены, но еще не определены. Таким образом, всякий раз, когда JS находит переменную, которая объявлена с помощью «var», но ей не присвоено никакого значения (также известное как «определенная»), она помещает «undefined» в качестве заполнителя.

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

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

Например:

var a = "Albus";

Это просто сохраняется в хранилище, как если бы в коде была переменная «a» без какого-либо значения (например, строка здесь).

Но в движке JavaScript есть специальный заполнитель для переменных, которые еще не определены, и это «undefined».

Следовательно, для всех намерений и целей,

a = undefined;

Это то, что произошло с «с» в нашем предыдущем блоке кода.

Но когда код начинает выполняться построчно, в это время значение «а» заполняется «Альбусом».

Вот так:

console.log(a); // undefined
var a = "Albus";
console.log(a); // "Albus"
console.log(b); // Reference Error ...not defined...

Обратите внимание, что код не говорит «не определено» для «а», а только для «б».

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

Мой код пытался получить доступ к «а» до того, как он был объявлен, и тем не менее мой движок признал существование «а», но не его значение, в отличие от «б», для которого он сказал, что его просто не существует.

«Первое чтение» технически называется «Фазой создания памяти», а «Второе чтение» технически называется «Фазой исполнения».

Поэтому заметьте, что когда JavaScript Engine находится на втором этапе, он уже запоминает все функции. Даже если функция объявлена в конце кода, это все равно память движка. Следовательно, мы можем вызвать функцию до того, как она будет объявлена.

calledHere(); // "This will be logged"

// ...lines of code...

function calledHere() {
  console.log('This will be logged');
}

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

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

Точно так же, как флаг поднят наверх, все функции доступны для вызова в любом месте кода без ошибок. Имейте в виду, что Engine на самом деле не перемещает код в начало файла, а просто ведет себя так, как если бы он это сделал.

Эта причуда должна наполнить вас множеством вопросов, таких как:

Происходит ли подъем внутри областей действия? Подняты ли классы? Каковы ошибки «инициализации»? Попробуем ответить на них по очереди.

Подъем в прицелах?

bcal();

var b = 1;

function bcal() {
  console.log(b);
  var b = 2;
}

Мы знаем, что функция «bcal» будет выполнена. Но как будет регистрироваться значение «b»?

Очень интересно, что он выведет «undefined», хотя я дважды объявил «b».

Все блоки кода имеют свои собственные две фазы. Если бы я не объявил «b» равным 2, он искал бы «b» в содержимом функции и, не найдя его, отправился бы на его поиск в глобальный контекст. Там он найдет значение «b» равным 1 и зарегистрирует его в консоли. Но на этапе создания памяти для блока «bcal» он вспомнил, что есть переменная с именем «b», но не то, что ее значение. Следовательно, это дало «неопределенное».

Я приглашаю вас поиграть с этим кодом, чтобы выяснить и подтвердить то, что я говорю. Слияние области действия и подъема — это очень весело и дает отличное представление о том, как более эффективно отлаживать код.

Как насчет занятий? Что такое «Инициализация»?

var thing = new Thing(1, 0);

class Thing{
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

console.log(thing);
// hoisted technically, but not initialized

Здесь отображается очень уникальный тип ошибки. Движок JavaScript сообщит вам, что класс существует, но вы объявили его после создания экземпляра. Это означает, что Engine знает, что класс существует, но не знает, что он содержит. Таким образом, экземпляр, который я объявил с помощью var, не просто «не определен» в качестве заполнителя, но фактически выдает ошибку. Довольно круто, правда?

Итак, классы подняты технически, т.е.: существование такого класса известно движку, но не то, что он содержит. Итак, мы говорим, что класс не инициализирован.

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

class Thing {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

var thing = new Thing(1, 2);

console.log(thing);

И это будет работать идеально.

Это поведение не наблюдается в скомпилированных языках, таких как Python, C++, Java и т. д.

Способ, которым сообщество JavaScript боролось с этой причудой «подъема», состоит в том, чтобы ввести «let» и «const» для объявления переменных и «стрелочные функции» для объявления функций. Если вы напишете свой код, используя их вместо «var» и «function», вам не придется беспокоиться о подъеме.

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

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