Не переоценивайте себя, этот вопрос немного сложный!

JavaScript

Учиться нет конца, почему их всегда так много нет?

В технической группе сегодня друг потерял вопрос и спросил, что на выходе?

На первый взгляд, на выходе всего 21. Думаете, добавив if(true), вы не знаете?

Тогда нет, а потом......

"Славный" - неправильный ответ!

Правильный ответ: 21 внутри и 1 снаружи;

Тайна действительно кроется в этом блочном условии if.

Если мы удалим if, чтобы увидеть вопрос.

var a = 0;
// if(true){
 a = 1;
function a(){}
a = 21;
console.log("里面",a);
// }
console.log("外部",a);

Подсчитано, что никто не стесняется задавать этот вопрос.Нет никаких сомнений, что выход а равен 21.

Однако в JS говорится, что нет области видимости на уровне блоков, так почему же блок if влияет на конечный результат?

Ниже приведена реализация под браузером chrome

Основные пункты этой статьи:

  • магия пусть
  • объем функционального блока

Что такое хостинг?

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

переменное продвижение

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

начальный уровень

Посмотрите на заголовок:

console.log(a);
var a = 0;

Это не сообщит: Uncaught ReferenceError: a не определено.

Вместо этого он выведет undefined.

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

var a;
console.log(a);
a = 0;

продвинутый класс

Пример 1:

var x = 0;
function a(){
    console.log(x);
    let x = 1;
}
a();

Если пусть x не поднимается, то x должен вывести 0, что на самом деле:

VM296:3 Uncaught ReferenceError: Cannot access 'x' before initialization at a (:3:14) at :1:1

Это не ошибка x не определено, но невозможно получить доступ.

Так в чем причина этой ошибки?

Пример 2:

let a = a;
let a = a;

Как вы думаете, о какой ошибке будет сообщено?

«a не определено» или «невозможно получить доступ к 'a' перед инициализацией»?

На самом деле ни то, ни другое, а ошибка: a уже объявлено.

Здесь это выглядит так: пусть также «подъем переменной», если она не поднимается, x в примере 1 должен выводить 0, а пример 2 должен сообщать об ошибке не определено.

Но если переменную можно продвигать, это неразумно, тогда вышеприведенный пример 1 должен выводить undefined .

Поэтому мы называем это «временной мертвой зоной».

На самом деле, это не переменный подъем в том смысле, в каком мы его понимаем, и нет никакого переменного подъема. Так что же такое временная мертвая зона?

Существует процесс "специального объявления" в определении переменных Let's. Когда JS выполняет предварительный разбор, определенные let и const "специальные объявления" выдвигаются первыми, подобно "поднимите руку", механизм JS указывает ту же область действия и одна и та же переменная может быть "поднята рука" только один раз.

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

Вернемся к этой теме.

let a = a; // 定义变量 a,我暂标识为 a1
let a = a; // 定义变量 a,我暂标识为 a2

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

Таким образом, это нарушает правило, согласно которому «одна и та же переменная может быть объявлена ​​только один раз в одной и той же области видимости», и об ошибке сообщается напрямую. На самом деле переменная, назначенная в коде, не была прочитана (при чтении переменной может выдаваться ошибка неопределенности переменной).

Итак, сообщается об ошибке, содержание ошибки: объявлено a2 (a объявлено a1).

Итак, вернемся к приведенному выше примеру 1, когда код считывает x, он обнаруживает, что x был объявлен с помощью let, но не был инициализирован, поэтому он напрямую сообщает об ошибке, что x недоступен.

Итак, что же за магия представляет собой «специальное объявление» переменной let?

На самом деле, это declareData, введенный движком JS для решения этого продвижения переменной let, Во время предварительного парсинга он сохраняет все данные объявлений let и const в области видимости.

Фактически, все функции и созданные переменные в области видимости должны быть проверены на соответствие значению declareData.

Пример 3:

var a = 1;//定义变量 a,我暂标识为 a1
let a = 2;//定义变量 a,我暂标识为 a2

declareData объявляет переменную а 2, а затем готовится определить переменную а 1. Обнаруживается, что декларация данных уже объявила а 2, и прямо сообщается об ошибке: а 1 уже объявлена, потому что переменная а была объявлена ​​​​а 2.

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

Подъем функций похож на подъем переменных, но немного отличается.

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

console.log(a);// undfined
var a = function (){}

console.log(a); // function a
function a(){}

Функциональные выражения не объявляют подъем, и первый пример выводит undefined вместо notdefined из-за подъема переменной в переменной var a.

область действия блока

console.log(a);// undefined
if(true){
    console.log(a); // function a
    function a(){}
}

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

var a; //  函数 a 的声明
console.log(a);// undefined
if(true){
    function a(){} // 函数 a 的定义
    console.log(a); // function a
}

Фактически, после того, как функция function a(){} предварительно проанализирована, объявление функции помещается в начало области видимости уровня функции, а затем определение функции перемещается в начало области действия уровня блока.

Примечание. Определение функции здесь вынесено на передний план области блока.

Посмотрите еще раз на вопрос:

try{
    console.log(a);// undefined
    aa.c;
}catch(e){
    var a = 1;
}
console.log(a);// 1
console.log(e);// Uncaught ReferenceError: e is not defined

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

В области JS есть область видимости на уровне блоков (помимо let const).

Посмотрите еще раз на оригинальное название

Чтобы было легче читать, повторю заголовок:

var a = 0;
if(true){
    a = 1;
    function a(){}
    a = 21;
    console.log("里面",a);
}
console.log("外部",a);

Объедините то, что мы узнали выше. Во-первых, функция a(){} в if объявит подъем, переместит объявление «var a» в начало области видимости уровня функции и переместит определение функции в начало области видимости блока. -parse выглядит следующим образом:

var a;// 函数 a 的声明提前
var a = 0;  // 已经声明了 a,这里会忽略声明 ,直接赋值为 0
if(true){
    function a(){} // 函数定义 a 声明提升到块级最前面
    a = 1; // 这里将 块级作用域最前面的函数 a 重置为 1了。
    // function a(){};  how do ?
    a = 21;
    console.log("里面",a);
}
console.log("外部",a);

Сама функция представляет собой [определить указатель переменной имени функции на блок памяти функции].

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

Предварительный разбор выглядит следующим образом:

var a = 0;
console.log(a,window.a); // 输出 0 和 0
if(true){
    console.log(a,window.a);// 函数提升,是块级作用域,输出 function a 和 0
    a = 1;  // 取作用域最近的块级作用域的 function a ,且被重置为 1了,本质又是一个 变量的赋值。
    console.log(a,window.a);// a 是指向块级作用域的 a, 输出 1 和 0 
    function a(){} // 函数的声明,将执行函数的变量的定义同步到函数级的作用域。
    console.log(a,window.a);// 输出 1 和 1
    a = 21; // 仍然是函数定义块级作用域的 a ,重置为 21
    console.log(a,window.a); // 输出为函数提升的块级作用域的 a, 输出 21,1
    console.log("里面",a);
}
console.log("外部",a);

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