Это 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). Помните, что если стек вызовов не пуст,тогда цикл событий не будет помещать в стек какие-либо обратные вызовы.
Теперь, с этими знаниями, давайте ответим на ранее упомянутые вопросы:
шаг:
- Вызов foo() помещает функцию foo в стек вызовов.
- При обработке внутреннего кода JS-движок обнаружил setTimeout.
- Затем передайте функцию обратного вызова foo в WebAPI (стрелка 1) и вернитесь из функции, стек вызовов снова пуст.
- Таймер установлен на 0, поэтому foo будет отправлено в очередь задач (стрелка 2).
- Поскольку стек вызовов пуст, цикл обработки событий примет обратный вызов foo и поместит его в стек вызовов для обработки.
- Процесс повторяется снова, и стек не переполняется.
Проблема 5: не отвечает
Разобрать:В большинстве случаев разработчики предполагают, что вВ графе цикла событий есть только одна очередь задач. Но это не так, у нас может быть несколько очередей задач. Браузер должен выбрать одну из очередей и обработать обратный вызов в этой очереди.
Под капотом JavaScript есть макрозадачи и микрозадачи. Обратный вызов setTimeoutзадача макроса, в то время как обратный вызов Promiseмикрозадачи.
Основное отличие заключается в том, как они выполняются. Макрозадачи помещаются в стек по одной за один цикл, но очередь микрозадач всегда очищается после выполнения перед возвращением в цикл обработки событий. Таким образом, если вы добавляете записи в эту очередь с той скоростью, с которой вы можете их обработать, вы всегда обрабатываете микрозадачи. Цикл событий будет повторно отображать страницу только тогда, когда очередь микрозадач пуста,
Теперь, когда вы запускаете следующий фрагмент в консоли
function foo() {
return Promise.resolve().then(foo);
};
Каждый вызов 'foo' продолжает добавлять еще один обратный вызов 'foo' в очередь микрозадач, поэтому цикл обработки событий не может продолжать обработку других событий (прокрутка, нажатие и т. д.), пока очередь не будет полностью очищена. Поэтому он блокирует рендеринг.