console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(function () {
console.log('promise1');
})
.then(function () {
console.log('promise2');
});
console.log('script end');
上面的题的打印顺序应该是什么呢?
为什么会这么打印? 而不是按顺序打印?
首先我先看一下js的事件循环是什么?
首先,js是单线程的,单线程的话就不会想多线程那样出现多个线程冲突等问题,但是单线程会出现----阻塞,就像马路上只有一条路,要是前一个车正好是是手动挡,起步慢,那后面的车不就得等着前一个车,但是js却没有这样的问题,js是非阻塞的,是因为js引进了异步与回调方式解决了这个问题。
// eventLoop是一个用作队列的数组
// (先进,先出)
var eventLoop = [ ];
var event;
// “永远”执行
while (true) {
// 一次tick
if (eventLoop.length > 0) {
// 拿到队列中的下一个事件
event = eventLoop.shift();
// 现在,执行下一个事件
try {
event();
}
catch (err) {
reportError(err); //异步:现在与将来
}
}
}
首先看一段事件循环(EventLoop)的伪代码,事件循环是一直执行的,每一次循环都是一个tick,任务开始执行,同步任务会被推送到主线程上执行,异步任务的回调函数会被推到任务队列中等待,等到主线程的任务执行完之后,执行栈空了,就会执行任务队列中的回调函数,将其推送到执行栈执行,一直这样循环着。
我们再来看一下上面的代码?
promise.then 和 定时器都是异步的任务 ,而且settimeout先被加入到任务队列中去,为什么promise.then会比settimeout要先执行?任务队列中的任务也有执行优先级?
原来任务队列中的任务也有宏任务与微任务之分
宏任务与微任务就是对执行栈任务的分类,宏任务就是由宿主环境提供的任务,比如浏览器,node环境,上面的代码中settimeout就是宏任务,而promise.then就是微任务,主线程进入执行栈就是一个宏任务,开始执行打印了’script start’,执行settimeout这是一个宏任务,放到宏任务队列,到promise.then这是一个微任务,放到微任务队列,执行 ‘script end’,然后到任务队列中去,看看有没有微任务,有的话就执行,执行了promise,then,最后执行下一个宏任务 settimeout
常见的宏任务与微任务
宏任务:主代码块,setTimeout,setInterval等
微任务:Promise.then,process.nextTick,MutationObserver等
我们在看一段代码
<div class="outer">
outer
<div class="inner">inner</div>
</div>
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');
new MutationObserver(function () {
console.log('mutate');
}).observe(outer, {
attributes: true,
});
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);
我们点击inner的话会打印什么?
1.inner点击 执行点击事件,执行里面的代码 打印了’click’
2.执行到settimeout 将settimeout 的回调放到任务队列中等待
3.执行到promise.then 将它的回调放到微任务队列中等待
4.给outer添加自定义属性,执行MutationObserver,MutationObserver是一个微任务,将它的回调放到微任务队列中,
5.任务执行完了,检查微任务队列执行微任务
6.执行promise.then的回调 打印 ‘promise’
7.执行MutationObserver回调 打印 ‘mutate’
8.事件冒泡 触发父元素的click事件
9.执行点击事件,执行里面的代码 打印了’click’
10.执行到settimeout 将settimeout 的回调放到任务队列中等待
11.执行到promise.then 将它的回调放到微任务队列中等待
12.给outer添加自定义属性,执行MutationObserver,MutationObserver是一个微任务,将它的回调放到微任务队列中
13.任务执行完了,检查微任务队列执行微任务
14.执行promise.then的回调 打印 ‘promise’
15.执行MutationObserver回调 打印 ‘mutate’
16.微任务执行完之后去任务队列中执行inner事件中settimeout的回调 打印’timeout’
17.执行outer事件中settimeout的回调 打印’timeout’
打印结果:
参考资料:
《你不知道的javascript中卷》
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
https://zhuanlan.zhihu.com/p/88510041