一、前言
javaScript 是单线程的编程语言,只能同一时间内做一件事情,按照顺序来处理事件,但是在遇到异步事件的时候,js线程并没有阻塞,还会继续执行,这又是为什么呢?
二、基础知识
堆:利用二叉树维护的一组数据,有两种:最大堆,最小堆,将根节点最大的堆叫最大堆,根节点最小的堆叫最小堆
栈:后进先出,只在某一端插入和删除的特殊线性表
队列:先进先出
进程和线程
一个进程可能包含多个线程。
浏览器的主要进程:
- 渲染进程:包含 JS引擎线程、事件触发线程、GUI线程、HTTP请求线程、定时器线程(为JS在浏览器中完成异步提供了基础)
- GPU进程:
- 插件进程:
- 网络进程:
微任务:new Promise().then(回调)、Process.nextTick()
宏任务:全局代码、setInterval、setTimeout、SetImmediate、I/O、UI Rendering、ajax、DOM事件监听
微任务和宏任务的区别:
微队列是唯一的,在整个事件循环中,仅存在一个,并且同一轮事件循环中的微任务会按顺序依次执行。而宏任务存在一定的优先级(用户I/O部分优先级更高),且同一轮事件循环中,只执行一个。
同步任务:在调用栈中按照顺序等待主线程依次执行
异步任务:异步任务在异步任务有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候被读取到栈内等待主线程执行。
执行栈:
当某个函数,用户点击一次鼠标、Ajax请求完成、一个图片加载完成等事件发生时,只要指定了回调函数,这些事件发生时就会进入执行栈队列中(是队列不是栈)
等待主线程读取,并遵循先进先出的原则。
主线程:
主线程和执行栈是两个不同的概念,主线程表示的是现在执行执行栈中的哪个事件。
主线程会不停的从执行栈中读取事件,并执行完成所有栈中的同步代码。
当遇到一个异步事件时,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列。
当主线程将执行栈中的所有任务都处理完,主线程回去任务队列中查看是否时任务,如果有,那么主线程会依次执行任务堆列中的回调函数。
三、浏览器中的事件循环
当JS代码执行时,所有任务(同步/异步)都在主线程上执行,形成一个执行栈。执行栈之外有用于存储待执行异步回调的任务队列: 宏任务队列、微任务队列。 浏览器中有其他分线程执行相关的管理模块:定时器管理模块、Ajax请求管理模块,DOM事件管理模块,若碰到这些任务源,就会将其回调函数加入到宏队列中。若碰到微任务如:promise 则将其添加到微任务队列中。直到Script宏任务执行结束后,就会执行微队列中的任务;当微队列中的所有微任务执行结束,就会检查宏队列中有没有可执行的宏任务。如果有,则执行该宏任务,之后检查微队列中并执行微任务,依次循环,反之,要是宏队列中没有待执行的任务,则循环结束。这就是我们常说的事件循环机制。
四、案例分析
console.log('同步代码1')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('同步代码2')
resolve()
}).then(() => {
console.log('promise.then')
)
console.log('同步代码3')
执行顺序:同步代码1、同步代码2、同步代码3、promise.then、setTimeout
解析:
1、同步任务优先于异步任务
2、微任务优先于宏任务
3、promise是同步任务,promise.then是异步任务