最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • eventLoop、宏任务、微任务 - 掘金

    正文概述 掘金(赵乐)   2021-11-10   442

    在解释标题前,我们先了解一下JavaScript的编译过程与运行机制: JavaScript引擎,不是逐条解释执行javaScript代码,而是按照代码块一段段解释执行。 一、编译阶段 对于常见编译型语言来说,编译步骤分为:词法分析->语法分析->语义检查->代码优化和字节生成。对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。当JavaScript解释器在构造语法树的时候,如果发现无法构造,就会报语法错误,并结束整个代码块的解析。 二、执行过程 在解释过程中,JavaScript引擎是严格按着作用域机制来执行的。JavaScript语法采用的是词法作用域,也就是说JavaScript的变量和函数作用域是在定义时决定的,而不是执行时决定的,由于词法作用域取决于源代码结构,所以JavaScript解释器只需要通过静态分析就能确定每个变量、函数的作用域,这种作用域也称为静态作用域。

    下面再来谈谈标题内容:

    **宏任务(macrotask):**又称为task,可以理解为每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。包括:script(整个代码块),I/O,xhr,setTimeout,setInterval,setImmediate(仅Node),requestAnimationFrame(仅浏览器),UI交互事件, postMessage, MessageChannel 微任务(microtask):又称为job,可以理解是在当前 task 执行结束后立即执行的任务。包括:Promise.then catch finally, await后面代码,process.nextTick(仅Node)

    注意:new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。async/await底层是基于Promise封装的,所以await前面的代码相当于new Promise,是同步进行的,await后面的代码相当于then,才是异步进行的

    Event Loop:JavaScript是单线程脚本语言,同一时间不能处理多个任务,所以何时执行宏任务,何时执行微任务?我们需要有这样的一个判断逻辑存在。这个判断逻辑被称为事件循环。

    eventLoop、宏任务、微任务 - 掘金

    事件循环的过程如下:

    1. JS引擎(唯一主线程)按顺序解析代码,遇到函数声明,直接跳过,遇到函数调用,入栈;
    2. 如果是同步函数调用,直接执行得到结果,同步函数弹出栈,继续下一个函数调用;
    3. 如果是异步函数调用,分发给Web API(多个辅助线程),异步函数弹出栈,继续下一个函数调用;
    4. Web API中,异步函数在相应辅助线程中处理完成后,即异步函数达到触发条件了(比如setTimeout设置的10s后),如果异步函数是宏任务,则入宏任务消息队列,如果是微任务,则入微任务消息队列;
    5. Event Loop不停地检查主线程的调用栈与回调队列,当调用栈空时,就把微任务消息队列中的第一个任务推入栈中执行,执行完成后,再取第二个微任务,直到微任务消息队列为空;然后
      去宏任务消息队列中取第一个宏任务推入栈中执行,当该宏任务执行完成后,在下一个宏任务执行前,再依次取出微任务消息队列中的所有微任务入栈执行。
    6. 上述过程不断循环,每当微任务队列清空,可作为本轮事件循环的结束。

    有几个关键点如下:

    1. 所有微任务总会在下一个宏任务之前全部执行完毕,宏任务必然是在微任务之后才执行的(因为微任务实际上是宏任务的其中一个步骤)。
    2. 宏任务按顺序执行,且浏览器在每个宏任务之间渲染页面
    3. 所有微任务也按顺序执行,且在以下场景会立即执行所有微任务
    • 每个回调之后且js执行栈中为空。
    • 每个宏任务结束后。

    我们通过几个示例来加深一下理解:

    setTimeout(_ => console.log(4))
    
    new Promise(resolve => {
      resolve()
      console.log(1)
    }).then(_ => {
      console.log(3)
    })
    
    console.log(2)
    

    流程如下:

    1. 整体script作为第一个宏任务进入主线程,遇到setTimeout入栈处理,发现是异步函数(宏任务),出栈,移交给Web API处理,0秒等待后,将回调函数加到宏任务队列尾部;
    2. 遇到new Promise,入栈处理,发现是同步任务,直接执行,console输出1;
    3. 遇到then,入栈处理,发现是异步函数(微任务),出栈,移交给Web API处理,将回调函数加入微任务队列尾部;
    4. 遇到console.log(2),入栈处理,同步任务,直接console输出2, 出栈;
    5. 栈已清空,检查微任务队列;
    6. 取出第一个回调函数,入栈处理,发现是同步任务,直接console输出3, 出栈;
    7. 继续从取微任务队列中取下一个,发现微任务队列已清空,结束第一轮事件循环;
    8. 从宏任务队列中取出第一个宏任务,入栈处理,发现是同步任务,直接console输出4;

    所以,最终输出结果为:1 > 2 > 3 > 4

    我们先稍微改变一下:

    setTimeout(_ => console.log(4))
    
    new Promise(resolve => {
      resolve()
      console.log(1)
    }).then(_ => {
      console.log(3)
      Promise.resolve().then(_ => {
        console.log('before timeout')
      }).then(_ => {
        Promise.resolve().then(_ => {
          console.log('also before timeout')
        })
      })
    })
    
    console.log(2)
    

    最终输出结果为:1 > 2 > 3 > before timeout > also before timeout > 4

    before timeout与also before timeout在4之前输出的原因是,在微任务执行的过程中,新产生的微任务会被直接添加到微任务队列尾部,并在下一宏任务执行之前,全部执行掉。
    而如果在微任务执行的过程中,新产生了宏任务,则会进入到宏任务队列尾部,按照宏任务顺序在后面的事件循环中执行。

    再来看一个嵌套的示例:

    Promise.resolve().then(()=>{
      console.log('Promise1')  
      setTimeout(()=>{
        console.log('setTimeout2')
      },0)
    })
    
    setTimeout(()=>{
      console.log('setTimeout1')
      Promise.resolve().then(()=>{
        console.log('Promise2')    
      })
    },0)
    

    最后输出结果是Promise1 > setTimeout1 > Promise2 > setTimeout2

    1. 一开始执行栈的同步任务执行完毕,会去 microtasks queues 找,清空 microtasks queues ,输出Promise1,同时会生成一个异步任务 setTimeout1
    2. 去宏任务队列查看此时队列是 setTimeout1 在 setTimeout2 之前,因为setTimeout1执行栈一开始的时候就开始异步执行,所以输出 setTimeout1
    3. 在执行setTimeout1时会生成Promise2的一个 microtasks ,放入 microtasks queues 中,接着又是一个循环,去清空 microtasks queues ,输出 Promise2
    4. 清空完 microtasks queues ,就又会去宏任务队列取一个,这回取的是 setTimeout2

    最后来一个复杂的示例,检测一下是否真正掌握了事件循环的机制。

    console.log('1');
    
    setTimeout(function() {
        console.log('2');
        process.nextTick(function() {
            console.log('3');
        })
        new Promise(function(resolve) {
            console.log('4');
            resolve();
        }).then(function() {
            console.log('5')
        })
    })
    process.nextTick(function() {
        console.log('6');
    })
    new Promise(function(resolve) {
        console.log('7');
        resolve();
    }).then(function() {
        console.log('8')
    })
    
    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,7,6,8,2,4,3,5,9,11,10,12。

    看个题目:

    async function a1 () {
        console.log('a1 start')
        await a2()
        console.log('a1 end')
    }
    async function a2 () {
        console.log('a2')
    }
    
    console.log('script start')
    
    setTimeout(() => {
        console.log('setTimeout')
    }, 0)
    
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    
    a1()
    
    let promise2 = new Promise((resolve) => {
        resolve('promise2.then')
        console.log('promise2')
    })
    
    promise2.then((res) => {
        console.log(res)
        Promise.resolve().then(() => {
            console.log('promise3')
        })
    })
    console.log('script end')
    

    结果(chrome浏览器中): script start

    a1 start

    a2

    promise2

    script end

    promise1

    a1 end

    promise2.then

    promise3

    setTimeout

    注意:每次我们使用 await, 解释器都创建一个 promise 对象,然后把剩下的 async 函数中的操作放到 then 回调函数中。

    参考文档:zhuanlan.zhihu.com/p/136366037


    起源地下载网 » eventLoop、宏任务、微任务 - 掘金

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元