предисловие
На самом деле понятие стека и кучи вначале было очень расплывчатым, только казалось, что оно связано с памятью, и казалось, что цикл обработки событий также задействован. Во время интервью с Mint интервьюер случайно задал этот вопрос, и в то время я мог только великодушно признать, что это не так. Подумав об этом, я вернулся и провел небольшое исследование.
мы начнемJS的内存机制
а также事件机制
и大量的🌰(例子)
Давайте разберемся, что такое стеки и кучи. Есть много понятий, поэтому вам не нужно читать их мертвыми, просто подумайте о них всем своим сердцем, и консоль браузера сделает их очень ясными.
погнали
Механизм памяти JS
Поскольку в JavaScript есть механизм автоматической сборки мусора, для фронтенд-разработки пространство памяти — это не та концепция, о которой часто упоминают и которую легко игнорируют все. В частности, многие непрофессиональные друзья будут иметь смутное представление о пространстве памяти после входа в интерфейс.
В JS для всех данных требуется место в памяти. Пространство памяти делится на два типа,стек памятиикуча памяти (куча).
Память стека обычно хранит основные типы данных
Number String Null Undefined Boolean
(es6新引入了一种数据类型,Symbol)
Самый простой 🌰
var a = 1
Мы определяем переменную a, и система автоматически выделяет место для хранения. Мы можем напрямую манипулировать значениями, хранящимися в пространстве памяти стека, поэтому доступ к базовым типам данных осуществляется по значению.
Хранение и использование данных в памяти стека аналогично структуре данных стека в структуре данных.последний пришел первый вышелправила.
Куча памяти обычно хранит ссылочные типы данных
🌰 кучи памяти
var b = { xi : 20 }
В отличие от других языков, эталонные типы данных JS, такие как массивы, не имеют фиксированного размера. Значения ссылочных типов данных — это объекты, которые хранятся в куче памяти. JavaScriptПрямой доступ к местам в куче памяти не разрешен, поэтому мы не можем напрямую манипулировать пространством кучи памяти объекта. Взгляните на изображение ниже для лучшего понимания.
сравнивать

var a1 = 0; // 栈
var a2 = 'this is string'; // 栈
var a3 = null; // 栈
var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中
Поэтому, когда мы хотим получить доступ к ссылочному типу данных в куче памяти, мы сначалаАдресная ссылка (или адресный указатель) объекта получается из стека, а затем получить нужные нам данные из кучи памяти.
контрольная работа
var a = 20;
var b = a;
b = 30;
console.log(a)
var m = { a: 10, b: 20 }
var n = m;
n.a = 15;
console.log(m.a)
Учащиеся могут играть в нее самостоятельно на консоли, а затем комбинировать следующую легенду, ее легко понять.
Мы понимаем механизм памяти, и возникает новый вопрос: можно ли хранить в стеке только базовые типы данных, а где функции, которые мы часто используем?
Механизм событий браузера
Часто упоминаемый вопрос на собеседовании🌰
console.log(1)
let promise = new Promise(function(resolve,reject){
console.log(3)
resolve(100)
}).then(function(data){
console.log(100)
})
setTimeout(function(){
console.log(4);
})
console.log(2)
Результат приведенной выше демонстрации равен 1 3 2 100 4
объектставитькуча (куча)внутри,Общие базовые типы и функцииставитькуча, когда функция выполняется вкучавыполнить в. Когда функция в стеке выполняется, она может вызывать некоторыеОперация Dom, операция ajax и таймер setTimeout, в это время вам нужно дождаться, пока все программы в стеке перейдут первыми** (примечание: код в стеке — первый вход, последний выход)**, а затем перейти к веб-API после обхода, а результаты выполнения WebAPI помещаются в очередь обратного вызова (callback queue).В очереди обратите внимание: код в очереди ставится первым и выполняется первым), то есть после завершения программы в стеке, событие считывается из очереди задач, а события в очереди помещаются в стек выполнения и выполняются последовательно, этот процесс цикличен.
- 1. Все задачи синхронизации выполняются в основном потоке, образуя стек выполнения
- 2. Помимо основного потока, есть еще и очередь задач. Как только асинхронная задача получает результат выполнения, событие помещается в очередь задач.
- 3. Как только все задачи синхронизации в стеке выполнения будут выполнены, система прочитает очередь задач, поместит события из очереди в стек выполнения и последовательно выполнит их.
- 4. Основной поток считывает события из очереди задач, что является непрерывным циклом.
Концепции вонючие и длинные, и это нормально, давайте бросим беглый взгляд, а затем посмотрим вниз.
Поставьте 🌰, чтобы проиллюстрировать, как выполняется стек
var a = "aa";
function one(){
let a = 1;
two();
function two(){
let b = 2;
three();
function three(){
console.log(b)
}
}
}
console.log(a);
one();
Результат демонстрации аа 2
диаграмма
Первое место в стеке выполнения занимает глобальная область видимости (выполнение кода имеет глобальную текстовую среду), затем помещается один, выполняется один и помещается два, выполняются два и затем помещаются три, один за другим.
Первым должно быть три, потому что, если два будут уничтожены первыми, код b из трех будет недоступен, поэтомупервый вышел последним(first-in-last-out), то есть сначала три выхода, затем два выхода, а затем один выход.
Что с очередью?
Еще один 🌰
console.log(1);
console.log(2);
setTimeout(function(){
console.log(3);
})
setTimeout(function(){
console.log(4);
})
console.log(5);
Код в стеке выполняется первым, 1 2 5. упомянутый ранееsettimeoutОн будет помещен в очередь, при выполнении стека будет добавлен из очереди в стек для исполнения (в данном случае выполняется последовательно), а 3 4
еще один 🌰
console.log(1);
console.log(2);
setTimeout(function(){
console.log(3);
setTimeout(function(){
console.log(6);
})
})
setTimeout(function(){
console.log(4);
setTimeout(function(){
console.log(7);
})
})
console.log(5)
Точно так же код синхронизации в стеке выполняется сначала 1 2 5. Точно так же самый внешний settimeout будет помещен в очередь, когда выполнение в стеке будет завершено, оно будет выполнено в стеке, 3 4. Вложенные 2 settimeouts будут помещены в новую очередь для выполнения 6 7.
Еще один взгляд 🌰
console.log(1);
console.log(2);
setTimeout(function(){
console.log(3);
setTimeout(function(){
console.log(6);
})
},400)
setTimeout(function(){
console.log(4);
setTimeout(function(){
console.log(7);
})
},100)
console.log(5)
Как и выше: порядок здесь 1, 2, 5, 4, 7, 3, 6. То есть, пока два установленных времени различаются, сначала будет выполнено короткое установленное время, включая функцию обратного вызова в наборе, а затем установленное время будет медленным. (потому что только когда время истечет, набор будет поставлен в очередь)
setTimeout(function(){
console.log('setTimeout')
},0)
for(var i = 0;i<10;i++){
console.log(i)
}
Результат этой демонстрации 0 1 2 3 4 5 6 7 8 9 setTimeout
Итак, в заключение всегдаКод в стеке выполняется первым, а затем читать события из очереди по очереди и добавлять их в стек для выполнения
После того, как стек (стек) будет закончен, очередь задач будет прочитана по очереди, а события в очереди будут помещены в стек выполнения и выполнены последовательно.В это время в стеке появляется событие, и это событие будет вызывать асинхронный в WebAPI метод, то эти асинхронные методы будут помещены в очередь при повторном вызове, а затем основной поток (то есть стек) будет считывать события из очереди задач по очереди после выполнения. процесс непрерывный.
Вернемся к нашему первому 🌰
console.log(1)
let promise = new Promise(function(resolve,reject){
console.log(3)
resolve(100)
}).then(function(data){
console.log(100)
})
setTimeout(function(){
console.log(4);
})
console.log(2)
Результат приведенной выше демонстрации равен 1 3 2 100 4
- Почему setTimeout выполняется после Promise.then?
- Почему новое обещание выполняется раньше, чем console.log(2)?
setTimeout — макрозадача, а Promise.then — микрозадача. Здесь new Promise() является синхронным, поэтому он выполняется немедленно.
Это открывает новую темузадача макросаимикрозадачи(Это также будет часто упоминаться в интервью)
Макрозадачи и микрозадачи
См. Задачи, микрозадачи, очереди и расписания (https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/?utm_source=html5weekly)
Концепция: и микрозадачи, и макрозадачи принадлежат очереди, а не стеку.
новый 🌰
console.log('1');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('2');
1 2 promise1 promise2 setTimeout
задача макроса
Чтобы обеспечить упорядоченное выполнение внутренних макрозадач JS и задач DOM, браузер будет повторно отображать страницу после выполнения задачи и до начала выполнения следующей задачи (задача->рендеринг->задача->… ) Щелчок мышью вызовет обратный вызов события, который должен выполнить задачу макроса, а затем проанализировать HTML. но,setTimeout отличается,Роль setTimeout заключается в ожидании заданного времени для создания новой задачи макроса для ее обратного вызова.. Вот почему он печатает 'setTimeout'существует'promise1 , promise2'после. потому что печатать 'promise1 , promise2' находится внутри первой задачи макроса и 'setTimeout'Даеще один новый независимыйпечатается в задании.
Микрозадачи
Микрозадачи обычно представляют собой задачи, которые необходимо выполнить сразу после завершения выполнения текущей задачи. Например, чтобы предоставить отзыв о серии действий или выполнить задачи асинхронно, не назначая новую задачу, вы можетеУменьшить накладные расходы на производительность. Пока в стеке выполнения не выполняется никакой другой код js и выполняется каждая макрозадача, очередь микрозадач будет выполняться немедленно. Если новая микрозадача добавляется в очередь микрозадач во время выполнения микрозадачи, новая микрозадача будет добавлена в конец очереди и будет выполнена позже. Микрозадача включает обратный вызов наблюдения за мутациями и следующие примеры.обещание обратного звонка.
Как только обещание имеет результат или уже имело результат (при результате это означает, что обещание достигло состояния выполнено или отклонено), оно сгенерирует микрозадачу для своего обратного вызова, которая гарантирует, что обратный вызов будет выполняться асинхронно даже если обещание Уже есть результаты. Таким образом, вызов .then() для обещания, которое уже имеет результат, немедленно создаст микрозадачу. Вот почему «promise1», «promise2» будут напечатаны после «конца сценария», потому что, когда все микрозадачи будут выполнены, текущий код стека выполнения должен быть выполнен. 'promise1', 'promise2' печатаются перед setTimeout, потому что все микрозадачи всегда выполняются до следующей макрозадачи.
еще 🌰
<div class="outer">
<div class="inner"></div>
</div>
// elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
//监听element属性变化
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
});
// click listener…
function onClick() {
console.log('click');
setTimeout(function() {
console.log('timeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise');
});
outer.setAttribute('data-random', Math.random());
}
//
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
click promise mutate click promise mutate (2) timeout
Хорошее объяснение, setTimeout будет добавлен в новую задачу макроса после завершения выполнения микрозадачи (Promise.then, MutationObserver.observe).
увидеть больше 🌰
console.log(1);
setTimeout(function(){
console.log(2);
Promise.resolve(1).then(function(){
console.log('promise1')
})
})
setTimeout(function(){
console.log(3)
Promise.resolve(1).then(function(){
console.log('promise2')
})
})
setTimeout(function(){
console.log(4)
Promise.resolve(1).then(function(){
console.log('promise3')
})
})
1 2 promise1 3 promise2 4 promise3
console.log(1);
setTimeout(function(){
console.log(2);
Promise.resolve(1).then(function(){
console.log('promise1')
setTimeout(function(){
console.log(3)
Promise.resolve(1).then(function(){
console.log('promise2')
})
})
})
})
1 2 promise1 3 promise2
Сводный обзор
-
куча:
- Сохраните базовый тип данных
- доступ по значению
- Сохраняемые значения имеют фиксированный размер
- Автоматически выделять память системой
- Небольшое пространство, высокая эффективность работы
- ФИФО, ЛИФО
- DOM, ajax и setTimeout в стеке попадут в очередь по очереди.Когда код в стеке будет выполнен, события в очереди будут помещены в стек выполнения и выполнены последовательно.
- Микрозадачи и макрозадачи
-
куча:
- хранить ссылочный тип данных
- доступ по ссылке
- Сохраненное значение является неопределенным и может быть динамически изменено
- В основном используется для хранения объектов
- Большое пространство, но относительно низкая эффективность работы
- Неупорядоченное хранилище, можно получить напрямую по ссылке
Широкая реклама
Эта статья была опубликована вЕженедельный выпуск Mint Front End, Добро пожаловать в Watch & Star ★, пожалуйста, указывайте источник при перепечатке.