什么是js事件循环
JavaScript有一个基于事件循环的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子任务。这个模型与其它语言中的模型截然不同,比如 C 和 Java。(来自MDN)
了解事件循环之前,我们要知道单线程、执行栈和任务队列
单线程
js是主要是在浏览器中运行的脚本语言,主要用途之一为操作dom,js的一大语言特点就是单线程,同一时间只做一件事情,这也被称为“阻塞式执行”,但是为什么js是单线程呢?
由于js里面有可视的Dom,如果是多线程的话,一个线程在删除Dom节点,另一个线程在编辑Dom,这个时候浏览器应该听谁的呢,这就促使js为单线程
执行栈
只要指定事件发生时就会进入执行栈队列,等待主线程读取,遵循先进先出原则
任务队列
由于js引擎是基于单线程执行的,所以所有任务进入队列都要等待,等待上一个任务执行完成才能执行。但是有些时候cpu也是空闲的,因为ajax操作从网络中读取数据,不返回结果不往下执行,这就使后面的任务即使是cpu空闲着也执行不了
因此js就将任务分为两种:同步任务与异步任务,
同步任务是:在主线程排队执行的任务,只有前一个任务执行完毕后,才能执行下一个任务,
异步任务是:进入任务队列中的异步任务,只有任务队列通知主线程某个异步任务可以执行了,该任务才会进入主线程执行
在js中的异步任务有:回调函数、定时器、事件绑定、大部分的ajax
宏任务和微任务
宏任务:script、setTimeOut、setInterval、setImmediate
微任务:promise.then,process.nextTick、Object.observe、MutationObserver
注意:Promise 是同步任务
宏任务和微任务是如何执行的
执行宏任务scrpit -> 所有同步任务在主线程中执行 -> 所有宏任务放入宏任务执行队列中 -> 所有微任务放入微任务执行队列中 -> 先清空微任务队列 -> 再取一个宏任务执行 -> 再清空微任务队列 -> 依次循环
定时器
以定时器为例,试着想想以下代码的输出顺序
setTimeout(function () {
console.log('1')
});
new Promise(function (resolve) {
console.log('2');
resolve();
}).then(function () {
console.log('3')
});
console.log('4');
new Promise(function (resolve) {
console.log('5');
resolve();
}).then(function () {
console.log('6')
});
setTimeout(function () {
console.log('7')
});
function bar() {
console.log('8')
foo()
}
function foo() {
console.log('9')
}
console.log('10')
bar()
解析
首先浏览器执行 Js 代码由上至下顺序,遇到 setTimeout,把 setTimeout 分发到宏 任务 Event Queue 中 new Promise 属于主线程任务直接执行打印 2
Promise 下的 then 方法属于微任务,把 then 分到微任务 Event Queue 中 console.log(‘4’)属于主线程任务,直接执行打印 4
又遇到 new Promise 也是直接执行打印 5,
Promise 下到 then 分发到微任务 Event Queue 中 又遇到 setTimouse 也是直接分发到宏任务 Event Queue 中,等待执行 console.log(‘10’)属于主线程任务直接执行 遇到 bar()函数调用,执行构造函数内到代码,打印 8
在 bar 函数中调用 foo 函数, 执行 foo 函数到中代码,打印 9 主线程中任务执行完后,就要执行分发到微任务 Event Queue 中代码,实行先进先出, 所以依次打印 3,6
微任务 Event Queue 中代码执行完,就执行宏任务 Event Queue 中代码,也是先进 先出,依次打印 1,7。
最终结果:2,4,5,10,8,9,3,6,1,