首页IT科技nodejs事件循环原理(带你深入理解js事件循环机制)

nodejs事件循环原理(带你深入理解js事件循环机制)

时间2025-06-17 07:04:24分类IT科技浏览3899
导读:同步任务和异步任务(微任务和宏任务)...

同步任务和异步任务(微任务和宏任务)

JavaScript是一门单线程语言

分为同步任务和异步任务

同步任务是指在主线程上排队执行的任务           ,只有前一个任务执行完毕                  ,才能继续执行下一个任务            。

异步任务指的是      ,不进入主线程           、而进入"任务队列"的任务;只有等主线程任务全部执行完毕     ,"任务队列"的任务才会进入主线程执行                 。

异步任务分为宏任务和微任务

new promise()                  、console.log()属于同步任务

宏任务(macrotask) 微任务(microtask) 谁发起的 宿主(Node      、浏览器) JS引擎 具体事件 1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage                  ,MessageChannel 5. setImmediate            ,I/O(Node.js) 1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js) 谁先运行 后运行 先运行 会触发新一轮Tick吗 会 不会

执行过程: 同步任务 —> 微任务 —> 宏任务

1.先执行所有同步任务     ,碰到异步任务放到任务队列中

2.同步任务执行完毕                 ,开始执行当前所有的异步任务

3.先执行任务队列里面所有的微任务

4.然后执行一个宏任务

5.然后再执行所有的微任务

6.再执行一个宏任务            ,再执行所有的微任务·······依次类推到执行结束      。

3-6的这个循环称为事件循环Event Loop

事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制

async/await (重点)

(个人注解:async/await 底层依然是 Promise                 ,所以是微任务                  ,只是 await 比较特殊)

async

当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象

async function test() {return 1 // async的函数会在这里帮我们隐士使用Promise.resolve(1) } // 等价于下面的代码 function test() { return new Promise(function(resolve, reject) { resolve(1) }) } // 可见async只是一个语法糖           ,只是帮助我们返回一个Promise而已

await

await表示等待                  ,是右侧「表达式」的结果      ,这个表达式的计算结果可以是 Promise 对象的值或者一个函数的值(换句话说           ,就是没有特殊限定)            。并且只能在带有async的内部使用

使用await时                  ,会从右往左执行      ,当遇到await时     , ★★★★★会阻塞函数内部处于它后面的代码                  ,去执行该函数外部的同步代码            ,当外部同步代码执行完毕     ,再回到该函数内部执行剩余的代码★★★★★, 并且当await执行完毕之后                 ,会先处理微任务队列的代码

示例

//1 console.log(1);//2 setTimeout(function() {console.log(2);process.nextTick(function() {console.log(3);})new Promise(function(resolve) {console.log(4);resolve();}).then(function() {console.log(5)}) }) //3 process.nextTick(function() {console.log(6); }) //4 new Promise(function(resolve) {console.log(7);resolve(); }).then(function() {console.log(8) }) //5 setTimeout(function() {console.log(9);process.nextTick(function() {console.log(10);})new Promise(function(resolve) {console.log(11);resolve();}).then(function() {console.log(12)}) }) // 先执行1 输出1 // 执行到2            ,把setTimeout放入异步的任务队列中(宏任务) // 执行到3,把process.nextTick放入异步任务队列中(微任务) // 执行到4                 ,上面提到promise里面是同步任务                  ,所以输出7,再将then放入异步任务队列中(微任务) // 执行到5           ,同2 // 上面的同步任务全部完成                  ,开始进行异步任务 // 先执行微任务      ,发现里面有两个微任务           ,分别是3                  ,4压入的      ,所以输出6 8 // 再执行一个宏任务     ,也就是第一个setTimeout // 先输出2                  ,把process.nextTick放入微任务中            ,再如上promise先输出4     ,再将then放入微任务中 // 再执行所以微任务输出输出3 5 // 同样的                 ,再执行一个宏任务setTImeout2            ,输出9 11 在执行微任务输出10 12 // 所以最好的顺序为:1 7 6 8 2 4 3 5 9 11 10 12 async function async1() {console.log( async1 start )await async2()console.log( async1 end ) } async function async2() {console.log( async2 ) } console.log( script start ) setTimeout( function () {console.log( setTimeout ) }, 0 ) async1(); new Promise( function ( resolve ) {console.log( promise1 )resolve(); } ).then( function () {console.log( promise2 ) } ) console.log( script end ) // 首先执行同步代码,console.log( script start ) // 遇到setTimeout,会被推入宏任务队列 // 执行async1(), 它也是同步的                 ,只是返回值是Promise                  ,在内部首先执行console.log( async1 start ) // 然后执行async2(), 然后会打印console.log( async2 ) // 从右到左会执行, 当遇到await的时候,阻塞后面的代码           ,去外部执行同步代码 // 进入new Promise,打印console.log( promise1 ) // 将.then放入事件循环的微任务队列 // 继续执行                  ,打印console.log( script end ) // 外部同步代码执行完毕      ,接着回到async1()内部, 继续执行 await async2() 后面的代码           ,执行 console.log( async1 end )                   ,所以打印出 async1 end                  。(个人理解:async/await本质上也是Promise      ,也是属于微任务的     ,所以当遇到await的时候                  ,await后面的代码被阻塞了            ,应该也是被放到微任务队列了     ,当同步代码执行完毕之后                 ,然后去执行微任务队列的代码            ,执行微任务队列的代码的时候,也是按照被压入微任务队列的顺序执行的) // 执行微任务队列的代码, 打印 console.log( promise2 ) // 进入第二次事件循环                 ,执行宏任务队列, 打印console.log( setTimeout ) /** * 执行结果为: * script start * async1 start * async2 * promise1 * script end * async1 end * promise2 * setTimeout */ console.log(1); async function fn(){console.log(2)new Promise((resolve)=>{resolve();}).then(()=>{console.log("XXX")})await console.log(3)console.log(4) } fn(); new Promise((resolve)=>{console.log(6)resolve(); }).then(()=>{console.log(7) }) console.log(8) // 执行结果为:1 2 3 6 8 XXX 4 7 /* 前面的 1 2 3 6 8 不再解析                  ,重点是后面的 XXX 4 7,由此可见 await console.log(3) 之后的代码 console.log(4) 是被放入到微任务队列了           , 代码 console.log("XXX") 也是被压入微任务队列了                  ,console.log("XXX")是在 console.log(4) 之前      , 所以当同步任务执行完毕之后           ,执行微任务队列代码的时候                  ,优先打印出来的是 XXX       ,然后才是 4       。 */ console.log(1); async function fn(){console.log(2)await console.log(3)await console.log(4)await console.log("await之后的:",11)await console.log("await之后的:",22)await console.log("await之后的:",33)await console.log("await之后的:",44) } setTimeout(()=>{console.log(5) },0) fn(); new Promise((resolve)=>{console.log(6)resolve(); }).then(()=>{console.log(7) }) console.log(8) /** * 执行结果为: * 1 * 2 * 3 * 6 * 8 * 4 * 7 * await之后的: 11 * await之后的: 22 * await之后的: 33 * await之后的: 44 * 5 */ /* 由此可见     ,代码执行的时候                  ,只要碰见 await             ,都会执行完当前的 await 之后     , 把 await 后面的代码放到微任务队列里面      。但是定时器里面的 5 是最后打印出来的                 , 可见当不断碰见 await             ,把 await 之后的代码不断的放到微任务队列里面的时候, 代码执行顺序是会把微任务队列执行完毕                 ,才会去执行宏任务队列里面的代码                 。 */ Promise.resolve().then(() => {console.log(0);return Promise.resolve(4) // 顺延2位如果是return 4 则打印 0     、1                  、4            、2     、3                 、5            、6、7 }).then(res => console.log(res)) Promise.resolve().then(() => {console.log(1); }).then(() => {console.log(2); }).then(() => {console.log(3); }).then(() => {console.log(5); }).then(() => {console.log(6); }).then(() => {console.log(7); }) /* 此题主要注意的是原生的Promise的then方法中                  ,如果返回的是一个普通值,则返回的值会被立即调用并赋值给resolve函数           , 如果返回的是一个thenable                  ,则then方法将会被放入到微队列中执行      , 如果返回的是一个Promise.resolve           ,则会再加一次微任务队列           。 即微任务后移                  ,Promise.resolve本身是执行then方法      ,而then方法本身是在微任务队列中执行     , 同时return Promise.resolve时是将resolve调用的返回值 作为上级then中resolve的参数传递                  , 调用外层then方法时本身是在微队列里面            ,所以函数的执行顺序是要在微队列中下移两次      。 */

根据w3c的最新解释

每个任务都有一个任务类型 , 同一个类型的任务必须在一个队列也就是一共有多个队列 , 不同类型的任务可以分属不同的队列,在一个次事件循环中,浏览器可以根据实际情况从不同的队列中区出任务执行 浏览器必须准备好一个微队列 , 微队列中的任务优先所有其他任务执行他里面的东西 所有都要给我等 连绘制任务 都要等 就是最高优先级了

随着浏览器的复杂度急剧提升 W3C不再使用宏队列的说法

在目前chrome的实现中 至少包含了下面的队列

延时队列 : 用于存放计时器到达后的回调任务 , 优先级中 交互列队 : 用于存放用户操作后产生的事件处理任务 , 优先级高 微队列 : 用户存放需要最快执行的任务 优先级最高

添加任务到微队列的主要方式主要是使用 Promise                 、MutationObserver

例如: // 立即把一个函数添加到微队列 Promise.resolve().then(函数)

任务有优先级吗?

任务没有优先级     ,在消息队列中先进先出 但消息队列是有优先级的 // 立刻把一个函数添加到微队列 最高执行 promise.resolve().then(函数) setTimeOut(()=>{ // 第三步执行延时队列中的任务console.log(1); },0) promise.resolve().then(()=>{ // 第二步执行微队列中的任务console.log(2); }) console.log(3); // 第一步先执行全局js // 3 2 1

面试题

1                  、如何理解 JS 的异步?

JS是一门单线程的语言                 ,这是因为它运行在浏览器的渲染主线程中            ,而渲染主线程只有一个                  。

而渲染主线程承担着诸多的工作,渲染页面、执行 JS 都在其中运行           。

如果使用同步的方式                 ,就极有可能导致主线程产生阻塞                  ,从而导致消息队列中的很多其他任务无法得到执行。这样一来,一方面会导致繁忙的主线程白白的消耗时间           ,另一方面导致页面无法及时更新                  ,给用户造成卡死现象                  。

所以浏览器采用异步的方式来避免                 。具体做法是当某些任务发生时      ,比如计时器           、网络                  、事件监听           ,主线程将任务交给其他线程去处理                  ,自身立即结束任务的执行      ,转而执行后续代码。当其他线程完成时     ,将事先传递的回调函数包装成任务                  ,加入到消息队列的末尾排队            ,等待主线程调度执行            。

在这种异步模式下     ,浏览器永不阻塞                 ,从而最大限度的保证了单线程的流畅运行                 。

2      、 阐述一下js的事件循环

事件循环又叫做消息循环            ,是浏览器渲染主线程的工作方式      。

在 Chrome 的源码中,它开启一个不会结束的 for 循环                 ,每次循环从消息队列中取出第一个任务执行                  ,而其他线程只需要在合适的时候将任务加入到队列末尾即可            。

过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境           ,取而代之的是一种更加灵活多变的处理方式                 。

根据 W3C 官方的解释                  ,每个任务有不同的类型      ,同类型的任务必须在同一个队列           ,不同的任务可以属于不同的队列      。不同任务队列有不同的优先级                  ,在一次事件循环中      ,由浏览器自行决定取哪一个队列的任务      。但浏览器必须有一个微队列     ,微队列的任务一定具有最高的优先级                  ,必须优先调度执行                 。

3           、JS 中的计时器能做到精确计时吗?为什么?

不行            ,因为:

1.计算机硬件没有原子钟     ,无法做到精确计时

2.操作系统的计时函数本身就有少量偏差                 ,由于 JS 的计时器最终调用的是操作系统的函数            ,也就携带了这些偏差

3.按照 W3C 的标准,浏览器实现计时器时                 ,如果嵌套层级超过 5 层                  ,则会带有 4 毫秒的最少时间,这样在计时时间少于 4 毫秒时又带来了偏差

4.受事件循环的影响           ,计时器的回调函数只能在主线程空闲时运行                  ,因此又带来了偏差

最后

整理了一套《前端大厂面试宝典》      ,包含了HTML                  、CSS      、JavaScript     、HTTP                  、TCP协议            、浏览器     、VUE                 、React            、数据结构和算法           ,一共201道面试题                  ,并对每个问题作出了回答和解析           。

有需要的小伙伴      ,可以点击文末卡片领取这份文档     ,无偿分享

部分文档展示:

文章篇幅有限                  ,后面的内容就不一一展示了

有需要的小伙伴            ,可以点下方卡片免费领取

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
神马关键词挖掘技巧(优化神马关键词排名靠,实现网站流量飙升!) 如何有效地运营维护网站?(关键策略与实用技巧)