Пять вопросов о JS

внешний интерфейс JavaScript
Пять вопросов о JS

Это 7-й день моего участия в Gengwen Challenge.Подробности о мероприятии:Обновить вызов

Вопрос 1: Что будет напечатано в консоли браузера?

var a = 10;
function foo() {
    console.log(a); // ??
    var a = 20;
}
foo();

Вопрос 2: Если мы используем let или const вместо var, будет ли результат таким же?

var a = 10;
function foo() {
    console.log(a); // ??
    let a = 20;
}
foo();

Вопрос 3: Какие элементы находятся в «newArray»?

var array = [];
for(var i = 0; i <3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??

Вопрос 4: Если мы запустим функцию «foo» в консоли браузера, вызовет ли это ошибку переполнения стека?

function foo() {
  setTimeout(foo, 0); // 是否存在堆栈溢出错误?
};

Вопрос 5: Если я запущу следующую функцию в консоли, будет ли по-прежнему реагировать пользовательский интерфейс страницы (вкладки)?

function foo() {
  return Promise.resolve().then(foo);
};

Отвечать:

Вопрос 1: не определен

Разобрать:Переменные, объявленные с помощью ключевого слова var, поднимаются в JavaScript и им присваивается значение undefined в памяти. Но инициализация происходит именно там, где вы присваиваете значения переменным. Кроме того, переменные, объявленные var, имеют [область действия] [2], в то время как let и const имеют область действия блока. Итак, вот как выглядит процесс:

var a = 10; // 全局使用域
function foo() {
// var a 的声明将被提升到到函数的顶部。
// 比如:var a

console.log(a); // 打印 undefined

// 实际初始化值20只发生在这里
   var a = 20; // local scope
}

Проблема 2: ReferenceError: a undefined.

Разобрать:

Объявления let и const позволяют ограничить переменную блоком, оператором или выражением, в котором она используется. В отличие от var эти переменные не поднимаются и имеют так называемую временную мертвую зону (TDZ). Попытка доступа к этим переменным в TDZ вызовет ошибку ReferenceError, поскольку доступ к ним возможен только тогда, когда выполнение достигает объявления.

var a = 10; // 全局使用域
function foo() { // TDZ 开始

// 创建了未初始化的'a'
    console.log(a); // ReferenceError

// TDZ结束,'a'仅在此处初始化,值为20
    let a = 20;
}

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

Вопрос 3: [3, 3, 3]

Разобрать:Объявление переменной с ключевым словом var в начале цикла for создает единую привязку (пространство для хранения) для этой переменной. Подробнее о [закрытиях]. Давайте снова посмотрим на цикл for.

// 误解作用域:认为存在块级作用域          
var array = [];
for (var i = 0; i < 3; i++) {
  // 三个箭头函数体中的每个`'i'`都指向相同的绑定,
  // 这就是为什么它们在循环结束时返回相同的值'3'。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

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

// 使用ES6块级作用域
var array = [];
for (let i = 0; i < 3; i++) {
  // 这一次,每个'i'指的是一个新的的绑定,并保留当前的值。
 // 因此,每个箭头函数返回一个不同的值。
  array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

Другой способ решить эту проблему — использовать [closed]

let array = [];
for (var i = 0; i < 3; i++) {

  array[i] = (function(x) {
    return function() {
      return x;
    };
  })(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

Проблема 4: Нет переполнения

Разобрать:Модель параллелизма JavaScript основана на «цикле событий». Когда мы говорим, что «браузер является домом для JS», я на самом деле имею в виду, что браузер предоставляет среду выполнения для выполнения нашего кода JS.

Основные компоненты браузера включают стек вызовов, цикл обработки событий**, очередь задач и веб-API**. Глобальные функции, такие как setTimeout, setInterval и Promise, не являются частью JavaScript, а частью Web API. Визуализация среды JavaScript выглядит так:在这里插入图片描述Стек вызовов JS работает по принципу «последним пришел — первым обслужен» (LIFO). Механизм берет одну функцию из стека за раз и выполняет код последовательно сверху вниз. Всякий раз, когда он сталкивается с каким-либо асинхронным кодом, таким как setTimeout, он передает его веб-API (стрелка 1). Таким образом, всякий раз, когда событие запускается, обратный вызов отправляется в очередь задач (стрелка 2).

цикл событий(Цикл событий) постоянно отслеживает очередь задач и обрабатывает обратные вызовы по одному в том порядке, в котором они стоят в очереди. в любое времястек вызовов(стек вызовов) пуст,Event loopполучить обратный вызов и ввести егокуча(стек) (стрелка 3). Помните, что если стек вызовов не пуст,тогда цикл событий не будет помещать в стек какие-либо обратные вызовы.

Теперь, с этими знаниями, давайте ответим на ранее упомянутые вопросы:

шаг:

  1. Вызов foo() помещает функцию foo в стек вызовов.
  2. При обработке внутреннего кода JS-движок обнаружил setTimeout.
  3. Затем передайте функцию обратного вызова foo в WebAPI (стрелка 1) и вернитесь из функции, стек вызовов снова пуст.
  4. Таймер установлен на 0, поэтому foo будет отправлено в очередь задач (стрелка 2).
  5. Поскольку стек вызовов пуст, цикл обработки событий примет обратный вызов foo и поместит его в стек вызовов для обработки.
  6. Процесс повторяется снова, и стек не переполняется.

Проблема 5: не отвечает

Разобрать:В большинстве случаев разработчики предполагают, что вВ графе цикла событий есть только одна очередь задач. Но это не так, у нас может быть несколько очередей задач. Браузер должен выбрать одну из очередей и обработать обратный вызов в этой очереди.

Под капотом JavaScript есть макрозадачи и микрозадачи. Обратный вызов setTimeoutзадача макроса, в то время как обратный вызов Promiseмикрозадачи.

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

Теперь, когда вы запускаете следующий фрагмент в консоли

function foo() {
  return Promise.resolve().then(foo);
};

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