前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS运行机制

JS运行机制

作者头像
ClyingDeng
发布2022-11-29 16:45:03
3.8K0
发布2022-11-29 16:45:03
举报
文章被收录于专栏:今天你敲代码了吗

本文阐述了浏览器端和node端的js运行机制执行的过程,还进行了两者的运行机制比较,以及同步任务和异步任务的说明,两种异步任务的必要性,以及各自有哪些回调,部分回调的优先级。

JS运行机制复述

首先js执行,会有一个函数执行栈(stack),一个任务队列(task queue),一个微任务队列(microtask queue),事件循环(event loop)。

  • 主线程:函数执行栈用来存放同步任务,按照后进先出的顺序执行;
  • 在任务队列中,存放的是宏任务。
  • 当函数执行栈为空时,会启动事件循环机制,将任务队列放到执行栈中执行。在此之前,每从任务队列中取一个任务时,如果微任务队列中存在任务,就先把微任务执行完成,在执行任务队列中的任务。
  • 依次循环,直到任务队列、微任务队列、函数执行栈均为空。

附:

「同步任务」:在主线程上执行的任务,只有前一个任务执行完成后才能执行下一个任务。 「异步任务」:不进入主线程执行,而是进入到任务队列(task queue)中执行。

Node.js中的事件循环

上段讲的是浏览器端的事件轮询,而node是多线程机制,由libuv库负责Node API的执行,将它分配给不同的线程,形成一个事件循环。

node中事件循环(event loop)大致分为六个部分。

  • timer定时器:执行setTimeout以及setInterval的回调。
  • I/O回调:处理网络、流、tcp错误等回调
  • idle空转和prepare阶段:node内部使用
  • poll轮询:执行poll中的I/O队列,检查定时器是否到时
  • check检查:存放setImmediate回调
  • close回调:关闭的回调

主要的是timer定时器、poll轮询、check 检查三大部分。在此我们只做了解。

事件循环过程:

  • 执行全局Script的同步代码。
  • 执行完同步代码调用栈清空后,执行微任务。「先执行NextTick队列」(NextTick Queue)中的所有任务,再执行其他微任务队列中的所有任务。
  • 开始执行宏任务,上面6个阶段。从第1个阶段开始,执行相应每一个阶段的「宏任务队列中所有任务」。(每个阶段的宏任务队列执行完毕后,开始执行微任务),然后在开始下一阶段的宏任务,依次构成事件循环。
  • timers Queue -> 执行微任务 -> I/O Queue -> 执行微任务 -> Check Queue 执行微任务 -> Close Callback Queue -> 执行微任务 ...

浏览器和Node端事件循环的差别

  • 两者的运行机制完全不同,实现机制也不同。
  • node.js可以理解成4个宏任务队列(timer、I/O、check、close)和2个微任务队列。但是执行宏任务时有6个阶段。
  • node.js在开始宏任务6个阶段时,每个阶段都将该宏任务队列中所有任务都取出来执行,每个阶段的宏任务执行完毕后,开始执行微任务。但是浏览器中的事件循环,是只取一个宏任务执行,然后看微任务队列是否存在,存在执行微任务,然后再取一个宏任务,构成循环。

JS异步任务

js的异步任务分为两种:宏任务、微任务。一个宏任务里面可以拥有多个微任务,在执行js代码块的时候才会去执行内部的微任务。

宏任务

macrotask,也叫tasks。一些异步任务的回调会依次进入宏任务队列,等待后续背调用。

宏任务包括:

  • setTimeout/setInterval
  • setImmediate(Node独有)
  • requestAnimationFrame(浏览器独有)
  • I/O
  • UI rendering(浏览器独有)

注意: 1、setTimeout延迟时间为0,与requestAnimationFrame比较:「requestAnimationFrame优先级大于setTimeout」

2、setTimeout延迟时间为0,与setImmediate比较:「不确定」

代码语言:javascript
复制
setTimeout(() => console.log('setTimeout'), 0)
setImmediate(()=>{
  console.log('setImmediate');
})

解释: timer前的准备时间超过1ms,(loop到timer的时间大于1ms),则执行timer阶段(setTimeout)的回调函数。 timer前的准备时间小于1ms,则先执行check阶段(setTimeout)的回调函数,下次事件循环,再执行timer阶段的回调函数。

如果想要setImmediate先执行,可以使用fs文件包裹,确保在I/O回调阶段执行。这样时间循环,会先执行chack阶段,之后再执行timer阶段。

node版本中的setTimeout

代码语言:javascript
复制
setTimeout(() => {
  console.log(1)
})
setTimeout(() => {
  console.log(2)
  Promise.resolve().then(function () {
    console.log('promise')
  })
})
setTimeout(() => {
  console.log(3)
})
  • node11以后的版本与浏览器端运行结果一致:1 2 promise 3。
  • node11之前的版本,执行结果为:1 2 3 promise。它会先进入timer阶段,执行第一个setTimeout并打印。再执行第二个setTimeout并打印,并将Promise放入微任务队列中。然后执行第三个setTimeout并打印。事件循环在执行下一阶段时,先执行微任务队列,打印promise。

微任务

microtask,也叫jobs。除宏任务外的一些异步回调会依次进入微任务队列,等待后续被调用。微任务包括:

  • process.nextTick(Node独有)
  • Promise.then()
  • Object.observe
  • MutationObserve

注意: process.nextTick优先级高于Promise.then()。

两种异步任务的必要性

在异步任务队列中,遵循先进先出的原则。此时,在众多异步任务中,如果存在优先级较高的任务需要优先执行,那么只有一个异步任务队列是无法满足的,此时就需要引入微任务队列,将优先级较高的任务放到微任务队列中。如果微任务队列非空,则执行微任务队列,否则执行宏任务队列。 如果只有一种异步任务,那么优先级高的异步任务无法优先执行。

补充

async/await

async/await本质上还是基于Promise的一些封装

「async」函数在await之前的代码都是「同步」执行的,可以理解为await之前的代码属于new Promise时传入的代码,「await之后的所有代码都是在Promise.then中的回调」

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 今天你敲代码了吗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JS运行机制复述
    • Node.js中的事件循环
      • 浏览器和Node端事件循环的差别
      • JS异步任务
        • 宏任务
          • node版本中的setTimeout
        • 微任务
          • 两种异步任务的必要性
          • 补充
            • async/await
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档