node【一文搞懂:浏览器和node的事件循环机制】【微任务和宏任务】
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。提示:以下是本篇文章正文内容,下面案例可供参考提示:这里对文章进行总结:例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
文章目录
前言
一般我们常说的事件循环其实分为两个概念,一个是node的事件循环,一个是浏览器的事件循环,。它们之间的实现原理是不一样的,如果有人问你,最好区别回答。
一、浏览器的事件循环
主线程
和事件循环线程
其实是同一概念的不同说法。不同的文章可能有不同的叫法,这里先说明。
1. 经典题目
开始之前先看一道经典考事件循环event loop
题目
setTimeout(() => {
Promise.resolve().then(()=>{
console.log(1)
})
setTimeout(() => {
console.log(2)
},0)
console.log(3);
},0)
setTimeout(() => {
console.log(4)
},0)
new Promise((resolve) => {
console.log(5)
resolve()
console.log(6)
}).then(()=>{
console.log(7)
})
console.log(8)
上面代码打印顺序为
5 6 8 7 3 1 4 2
怎么样是不是猜对了,下面我来来讲讲为什么会是这样
2. 微任务和宏任务
要了解上面代码执行结果的原因,搜先要知道什么是微任务(micro task)和宏任务(macro task)。
它们是由执行时机去区分的。
- 宏任务:在下一次事件循环执行的就是宏任务
- 微任务:在本次事件循环执行结束时
(即所有同步代码执行后执行)
的就是微任务
3. 如何产生宏任务和微任务
这些方法可以产生宏任务
-
setTimeout(cb)
:它的回调函数是宏任务,它本身是个同步方法 -
setInterval(cb)
:它的回调函数是宏任务,它本身是个同步方法 -
requestAnimationFrame(cb):
高性能js动画方法,根据刷新率,cb回调方法定时执行一次 -
事件监听的
回调函数
:dom点击事件绑定函数等,点击按钮后,js将回调函数开启为一个宏任务 -
Ajax、FileRead 的回调函数
:使用XMLHttpRequst发送请求或这FileRead读取文件,实际是开启了一个异步操作,这个和微任务宏任务没关系,操作执行完毕后会将它们的回调函数
做为一个宏任务
这些方法可以产生微任务
Promise的 .then .catch .finally
Async
:async函数内部await之前的语句都是同步执行async function fn(){ console.log("1") await console.log("2") console.log("3"); } fn(); //同步打印 1 2 异步打印 3
Generator
:异步函数旧语法,async是它的升级版MutationObserver:
用于监听dom属性变化,不怎么常用了解就行
4. 事件循环流程
在浏览器上,所有的js文件最总都会化为javascript标签中的代码,下面我们就来逐步讲解js引擎执行js代码的步骤。
- 浏览器加载一个html时,会创建一个js引擎环境。在js引擎环境初始化,会创建调用栈(
call stack
)和任务队列(task queue
)。微任务
和宏任务
有不同的任务队列
script
标签中的代码会作为一个宏任务
加入任务队列
,并立即推入调用栈
中,开始执行(由主线程执行代码,主线程再js引擎初始化时创建)- 当
调用栈
中遇到创建宏任务
的代码(setTimeout
等),会在宏任务队列
中加入一个宏任务
。当遇到创建微任务
的代码(Promise
等),会在微任务队列
中加入一个微任务
调用栈
中本次宏任务
所有同步代码执行完毕后,事件循环开始逐个执行将微任务队列
中的微任务
,压入调用栈
开始执行。微任务
中如果存在创建微任务
和宏任务
的代码同步骤3
- 当所有
微任务代码
执行完毕后,从宏任务队列
中取出最先压入的宏任务
开始执行,开始循环3-4步骤。这个循环机制
就是事件循环
有张图可能比较直观
值得一提的是,只会有一个宏任务队列和微任务队列,
微任务
会在本次宏任务所有代码执行后,顺序执行微任务队列中的所有微任务,但它们不属于包含关系,它们分别拥有自己的任务队列。
二、node的事件循环
浏览器中有事件循环,node 中也有,事件循环是 node 处理非阻塞 I/O 操作的机制,node中事件循环的实现是依靠的libuv引擎。由于 node 11 以及11之后,事件循环的一些原理发生了变化,这里就以新的标准去讲,但是可以最为一个面试点显得你好学~,最后再列上变化点让大家了解前因后果。
1. 创建微任务和宏任务
node环境中创建微\宏任务的方法和浏览器差不多,没有requestAnimationFrame
、MutationObserver
。但是多了一些特有的方法
宏任务
setImmediate:
在check阶段执行I/O
操作等:一般在poll和pending阶段执行EventEmitter
:所有的事件回调函数
微任务
process.nextTick(cb)
:这个比较特殊他会在下一次事件循环中所有微任务之前执行
node中宏任务的执行机制与浏览器中有所差别,但是微任务的执行机制是一样的,会在当前宏任务执行后,下个宏任务开始之前执行
2. 事件循环实现机制
node中事件循环和浏览器的事件事件循环机制不一样,因为要和内核打交流,所以更加复杂但总体来说分为6个阶段顺序执行
- 每个阶段都有自己的宏任务队列,它们在内存中是独立的对象。这个对象在node.js进程启动时就已经创建,并不会在每次事件循环滴落中重新创建
- 与宏任务队列不同,
事件循环
中只存在一个微任务队列
,它在内存中的储存对象也是在node进程启动时就已经创建。所有宏任务中产生的微任务,都放在同一个微任务队列中,它们并不是一个宏任务关联一个微任务队列。 - 每个阶段中,每个宏任务执行完毕后,在下一个宏任务执行之前,会先顺序执行微任务队列中的所有微任务。
node在开始执行脚本的时候,会将最外层的代码作为第一个宏任务放到任务队列中,并立即取出执行
-
timers
此阶段检查定时器(setTimeout、setInterval)中时间是否到期,如果有到期的定时器任务,则执行回调,否则进入下一阶段 -
pending(io/callbacks)
执行上一轮事件循环遗留的 I/O 回调。根据 Libuv 文档的描述:大多数情况下,在轮询 I/O 后立即调用所有 I/O 回调,某些情况下比如读取大文件,一次事件循环可能无法处理完成,此类回调会推迟到下一次循环迭代。听完更像是上一个阶段的遗留。 -
idle,prepare
只供libuv内部使用,可以忽略 -
poll
这个阶段比较重要,它有两个作用执行i/o操作的回调函数
和等待异步任务的回调
当此阶段任务队列中存在任务(i/o操作的回调函数)时,按任务队列中的顺序执行任务,执行完毕后。
- 如果
其他阶段没有任务
,
比如有一个setTimeout(cb,5000)
。五秒后才会在timer阶段
的任务队列
推入任务。而在此期间,其余各个阶段
的任务队列
中都没有任务。那么事件循环机制
会在此等待,直到5秒
后timers
阶段的任务队列
中出现任务
, 才进入timer阶段
。 - 如果某个
阶段
(除poll
外)没有任务,则会直接跳过此阶段,所以这里直接跳过check close callbacks
阶段进入下一个事件循环 - 进入下一个事件循环后,此时其他
阶段
包括poll
阶段都没有任务需要执行,又因为5秒timers
阶段会推入一个任务,所以node进程不会退出,直接跳过timers
和i/o callbacks
进入poll
阶段等待。 - 如果所有io任务都已执行完毕,包括所有的定时器,那么node会退出进程
- 如果
-
check
这个阶段执行setImmediate中的回调函数 -
close callbacks
此阶段执行关闭请求的回调函数,比如socker.on('close',cb)
,此阶段执行完毕后又开始执进入timers
阶段,形成循环
node为什么会知道5秒后会timers阶段会插入一个任务,nodejs的所有异步操作,包括I/O操作、定时器等,都是由libuv库来管理,这个库是由c++(超纲了)写的一个高性能库。你只需要知道它实现了这个功能
值得一提的是,在主模块中
setTimeout(cb)
在setImmdiate(cb)
要早执行,因为在一个事件循环是timers
阶段比check
阶段更快执行。而在i/o
的回调函数中(readFile(cb)
),已经处于事件循环的poll
阶段,所以处于check
阶段执行的setImmdiate(cb)
要更快执行,setTimeout(cb)
需要到下个循环的timers
阶段执行
3. node11以前的不同
在node11以前,node事件循环处理微任务的方式有点不一样。
假定一个阶段中存在多个宏任务
setImmediate(() => {
log('setImmediate1');
Promise.resolve('Promise microtask 1')
.then(log);
});
setImmediate(() => {
log('setImmediate2');
Promise.resolve('Promise microtask 2')
.then(log);
});
在node10以及以前执行结果是
setImmediate1
setImmediate2
Promise microtask 1
Promise microtask 2
node11及以后
setImmediate1
Promise microtask 1
setImmediate2
Promise microtask 2
在node10及以前会先将阶段内所有的宏任务执行完毕后在执行微任务。
在node11及以后一个宏任务执行完毕之后,立即执行微任务,微任务执行完毕再执行下一个宏任务
三、值得思考的问题
1. 浏览器中所有任务执行完毕之后会怎样
- 和node不一样,因为浏览器中存在交互,你不知道什么时候用户就会点击一个按钮,所以当主线程执行完所有任务队列后,它并不会"死去",而是会进入等待状态,等待新的任务加入到任务队列中。
- 新的任务可以由各种事件触发,比如用户的点击事件、网络请求返回等。当这些事件发生时,会有相应的回调函数被加入到任务队列中,然后由主线程执行。
- 只有当浏览器标签页被关闭后,主线程才会死去
更多推荐
所有评论(0)