JS 微任务和宏任务
EventLoop
浏览器是由多个进程组成的
EventLoop 解决的是 js 执行时可能会调用异步方法,这些方法是怎样调度执行的
1.js 执行的时候 会从上到下执行,遇到函数会创建执行上下文放入到执行栈中,执行完毕后会出栈 执行时可能会发生异步事件 - 》 内部会调用浏览器 Api
2.当我们执行上下文栈都执行完毕后, 等会可能 api 执行完成或者时间到达, 会被我 IE 互道一个事件“队列”、消息“队列” 先进先出
3.不停的扫描队列,将队列里的任务拿出来放到上下文栈中执行
事件循环线程是专门干这件事的,检测当前执行栈是否为空,如果为空,从事件队列中取出一个来执行 setTimeout 宏任务
4.当代码执行时还会有一些任务 promise 为例,微任务,每次执行宏任务的时候 都会单独创建一个微任务“队列” 先进先出
5.微任务在执行完毕后,浏览器会检测是否要重新渲染,浏览器有刷新频率 大约 16.6ms
6.每次循环一次都会执行一个宏任务,并清空对应的微任务队列,每次循环完毕后,都要看是否要渲染,如果需要渲染才渲染
宏任务 script 脚本 界面渲染也是宏任务 setTimeout setInterval postMessage MessageChannel setImmediate 也是宏任务 事件 ajax
微任务: promise.then mutationObserver
微任务中在执行时再生成微任务,会在本轮直接清空
线程和进程
进程和线程,一个进程包含多个线程,进程是计算机分配任务的最小调度单位。线程是操作系统 CPU 进行运算调度的最小单位,一个应用不止一个进程,比如浏览器的每一个页卡都是一个进程,保证互相之间独立。
为什么 js 是单线程执行
js 主线程是单线程,执行 js 代码的时候,默认是单线程在从上到下依次执行
浏览器在渲染页面的时候,会有一个渲染线程(浏览器内核)在工作,渲染线程在执行的时候,js 引擎就会阻塞等待,js 代码的执行和页面的渲染是互斥的。
如果 js 是多线程工作的,线程一要求新增一个 dom,线程二要求删除一个 dom,这个时候渲染线程就不知道怎么办
宏任务和微任务
宏任务,宿主环境提供的,比如浏览器
微任务,语言本身提供的,比如 promise.then
js 在执行到 ajax 这种异步方法时,先不去执行,把它存放到宏任务队列中,在同步代码全部执行完毕后,再去执行
常见的宏任务:ajax、setTimeout、setIntervar、requestAnimationFrame、messageChannel、UI 渲染、setImmediate(只在 IE 下才会执行)
常见的微任务:then、queueMicrotask(基于 then)、mutationObserver(浏览器提供)
事件环
node 中处理非阻塞 I/O 的操作机制
node 启动后,就会初始化事件环
执行流程
先进行主栈代码的执行,注意主栈代码也属于宏任务,微任务是语言提供的
主栈代码执行后,会清空一次微任务队列,在清空微任务之前会先执行 process.nextTick(优先级高于微任务,不属于事件环的一部分),然后会进入事件环执行
node 中的事件环包含以下几个阶段,timers、pending callbacks、idle,prepare、poll、close callbacks
timers 阶段,存放的是 setTimeout、setInterval 回调队列
pending callbacks、close callbacks 不受我们控制的,系统内部控制
poll 阶段,存放异步 I/O 操作队列
check 阶段,存放 setImmediate 队列
事件环的执行是从上到下依次执行的,如果主栈代码执行完毕后,这个时候 timers 中的队列为空,也就是定时器的等待时间还未到,会往下继续执行到 poll 阶段,查看异步队列是否有没有完成的任务吗,如果有就先处理 poll 中的队列,poll 中的处理任务存在上限,最多当执行到 99 个的时候,会先退出 poll 阶段的执行,往下执行,如果 poll 阶段没有内容就执行 check 阶段的检查,如果 check 阶段队列存在任务,就执行,执行完毕后回到 timers 阶段继续事件环的执行,最终停在 poll 阶段阻塞等待
注意,上面的几个队列中存放的都是宏任务,每执行完一个宏任务,就清空一次微任务队列,在老的 node 版本中,当整个阶段的队列都执行完毕后才会去清空一次微任务队列。
process.nextTick 作用
可以阻止立即从构造函数中触发事件,因为此时脚本还没有执行到为这个事件分配回调函数的地方
二者之间的关系
node10 版本之后的事件环和浏览器的事件环执行结果一样了,但是本质不一样