说起事件循环,可从以下三方面来回答和学习
- 基础知识:
js单线程、异步编程、事件循环任务类型,宏任务和微任务 - 事件循环过程
- 程序执行顺序:输出结果以及阐述运行流程
1、基础知识
1.1 js单线程
javascript是单线程的,就意味着同一时间只能做一件事。如果上面的代码很耗时如定时器、网络请求等,下面的代码就无法执行。
为了防止javascript的主线程不阻塞,eventLoop(事件循环),任务队列产生。
1.2 异步的概念及目的
异步:程序中现在运行的部分和将来运行的部分之间的关系,就是异步编程的核心。
目的:异步的出现,是为了解决程序阻塞
程序中将来执行的部分,并不一定在现在运行的部分执行完之后就立即执行。换句话来说,现在无法完成的任务将会异步完成,因此并不会出现人们本能认为的阻塞行为。
异步解决方式:从现在到将来的“等待”,最简单的方法是使用一个通常成为回调函数的函数。后续出现promise、async await等优秀的解决方案。
1.3 异步机制的引入
任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点击、ajax响应等)时执行,你就是在代码中创建了一个将来执行的块,也由此在这个程序中引入了异步机制。
1.4 事件循环的任务类型
在js中把任务分为了同步任务和异步任务
同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行
异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时器等
1.5异步任务包括两种类型:宏任务和微任务
宏任务和微任务区别如下:
| 宏任务 | 微任务 | |
|---|---|---|
| 谁发起的 | 宿主(Node , 浏览器Window) | JS引擎 |
| 具体事件 | 1、定时器setTimeout、setInterval 2、 script全部代码(外层同步代码)3、 DOM事件4、 HTTP请求(ajax、fetch、jsonp) | 1、Promise (then、catch、finally...)2、 async await3、 Process.nextTick(Node独有)4、 mutationObserver(html5新特性) |
| 谁先运行 | 后运行 | 先运行 |
会触发新一轮的tick吗 | 会 | 不会 |
注:
js运行宿主:
javascript引擎并不是独立运行的,它运行在宿主环境中,对多数开发者来说通常就是web浏览器。近几年出现了其他的环境,如node.js。
但所有的环境都有一个共同点,他们都提供了一种机制来处理程序中多个块的执行,且执行每块时调用javascript引擎,这种机制称为事件循环
换句话说,javascript引擎本身并没有时间的概念,只是一个按需执行js任意代码片段的环境,“事件”调度总是由包含它的环境进行。
2、事件循环过程
事件循环步骤:
1、执行主线程代码,执行同步任务代码。遇到微任务放到微任务事件队列,遇到宏任务放到宏任务事件队列。所有主线程任务完成之后,接下来读取异步队列中的异步任务
2、去异步微任务队列中查找。找到可执行条件的异步微任务,交给主线程执行
3、去异步宏任务队列中查找。找到可执行条件的异步宏任务,交给主线程执行
第一个图为步骤一(主线程和异步队列)
第二个图为步骤二和三(异步队列:宏任务和微任务)
3、程序执行顺序
3.1 promise和setTimeout执行顺序
setTimeout(function () {
console.log(1);
}, 0);
new Promise(function (a, b) {
console.log(2);
for (var i = 0; i < 10; i++) {
i == 9 && a();
}
console.log(3)
}).then(function () {
console.log(4)
});
console.log(5);
运行结果:
2 3 5 4 1
代码分析:
setTimeout宏任务,放到异步宏任务队列中- 执行
Promise,输出2。then()放到异步微任务队列中 - 执行代码,输出5
- 同步代码执行完成后,执行异步微任务队列,输出4
- 5.执行异步宏任务代码输出1
3.2 async await执行顺序
async function async1() {
const res = await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
运行结果:
async2 end Promise async1 end promise1 promise2 setTimeout
代码分析:
1. 执行代码,执行async1(),调用async2(),输出async2 end, 并返回一个Promise。遇到await会让出主线程,将await async2()放到异步微任务队列中
2. setTimeout是宏任务,放到异步宏任务队列中
3. Promise构造函数,输出Promise。两个then()方法是异步微任务,放到异步微任务队列中。
4. 同步代码执行完成,执行异步微任务队列,按照队列的先进先出的原则,依次输出 async1 end promise1 promise2
5. 执行异步宏任务队列,输出setTimeout
3.3 promise嵌套setTimeout
console.log('script start')
setTimeout(function () {
console.log('timeout1')
}, 10)
new Promise((resolve) => {
console.log('promise1')
resolve()
setTimeout(() => {
console.log('timeout2')
}, 10)
}).then(function () {
console.log('then1')
})
console.log('script end')
运行结果:
script start promise1 script end then1 timeout1 timeout2
相信看到这里,最后一个题已经是小case了,就不阐述分析过程啦。
本文是在我梳理Promsie过程中,发现需要回顾异步及事件循环写下的一篇文章。如果想对Promise有更深入的了解,可以看我的《从浅入深的promise》这系列的文章。
本人能力有限,尚且不能深入解读,如果想要深入学习,可以关注冴羽的javascript深入系列:juejin.cn/column/7035…