zoukankan      html  css  js  c++  java
  • js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)

    javascript是单线程,一切javascript版的"多线程"都是用单线程模拟出来的,通过事件循环(event loop)实现的异步。

    javascript事件循环

    事件循环中的同步任务,异步任务:

    • 同步和异步任务在不同的执行"场所",同步的进入主线程,异步的进入Event Table执行并注册函数。

    • 当指定的异步事情完成时,Event Table会将这个函数移入Event Queue

    • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,推入主线程执行。

    • js引擎的monitoring process进程会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。上述过程会不断重复,也就是常说的Event Loop(事件循环)。

    用个例子说明上述过程:

    let data = [];
    $.ajax({
        url:www.javascript.com,
        data:data,
        success:() => {
            console.log('发送成功!');
        }
    })
    console.log('代码执行结束');
    • ajax(异步任务)进入Event Table,注册回调函数success。

    • 执行console.log('代码执行结束')。(同步任务在主线程执行)

    • ajax事件完成,回调函数success进入Event Queue。

    • 主线程从Event Queue读取回调函数success并执行。

    setTimeout和setInterval中的执行时间

    console.log('先执行这里');
    setTimeout(() => {
        console.log('执行啦')
    },3000);

    //先执行这里
    // ... 3s later 3秒到了,计时事件timeout完成,定时器回调进入Event Queue,主线程执行已执行完,此时执行定时器回调。
    // 执行啦

    setTimeout明明写的延时3秒,实际却5,6秒才执行函数,这咋回事?

    setTimeout(() => {
        task()
    },3000)
    
    sleep(10000000)

    上述代码在控制台执行task()需要的时间远远超过3秒,执行过程:

    • task()进入Event Table并注册,计时开始。

    • 执行sleep函数,很慢,非常慢,计时仍在继续。

    • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。

    • sleep终于执行完了,task()终于从Event Queue进入了主线程执行。

    上述的流程走完,setTimeout这个函数是经过指定时间后,把要执行的任务(本例中为task())加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。

    重点来了:定时器的毫秒,不是指过ms秒执行一次fn,而是过ms秒,会有fn进入Event Queue。

    setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

    关于setTimeout要补充的是,即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒

    对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。

    宏任务和微任务

    除了广义的同步任务和异步任务,我们对任务有更精细的定义:

    • macro-task(宏任务):包括整体代码script,setTimeout,setInterval

    • micro-task(微任务):Promise,process.nextTick

    不同类型的任务会进入对应的Event Queue,比如setTimeout和setInterval会进入相同的Event Queue。

    在宏任务和微任务概念中的事件循环机制:

    主任务(宏任务)完——所有微任务——宏任务(找到宏任务其中一个任务队列执行,其中如果又有微任务,该任务队列执行完就执行微任务)——宏任务中另外一个任务队列(里面偶微任务就再执行微任务)。

    总的来说就是在宏任务和微任务之间来回切。下面列子执行过程:

    第一轮:主线程输出:【1,7】,添加宏任务【set1,set2】,添加微任务【6,8】。执行完主线程,然后执行微任务输出【6,8】

    第二轮:执行宏任务其中一个任务队列set1:输出【2,4】,执行任务的过程,碰到有微任务,所以在微任务队列添加输出【3,5】的微任务,在set1宏任务执行完就执行该微任务,第二轮总输出:【2,4,3,5】

    第三轮:执行任务另一个任务队列set2:输出【9,11】,执行任务的过程,碰到有微任任务,所以在微任务队列添加输出【10,12】的微任务,在set2宏任务执行完就执行该微任务,第三轮总输出:【9,11,10,12】

    整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

     console.log('1'); //第一轮主线程【1】

    setTimeout(function() { //碰到set异步,丢入宏任务队列【set1】:我将它命名为set1
        console.log('2');//第二轮宏任务执行,输出【2】
        process.nextTick(function() {//第二轮宏任务执行,碰到process,丢入微任务队列,【3】
            console.log('3');
        })
        new Promise(function(resolve) {//第二轮宏任务执行,输出【2,4】
            console.log('4');
            resolve();
        }).then(function() {
            console.log('5')//第二轮宏任务执行,碰到then丢入微任务队列,【3,5】
        })
    })
    process.nextTick(function() { //碰到process,丢入微任务队列【6】
        console.log('6'); //第一轮微任务执行
    })
    new Promise(function(resolve) {
        console.log('7'); //new的同时执行代码,第一轮主线程此时输出【1,7】
        resolve();
    }).then(function() {
        console.log('8') //第一轮主线程中promise的then丢入微任务队列,此时微任务队列为【6,8】。当第一轮微任务执行,顺序输出【6,8】
    })

    setTimeout(function() { //碰到set异步丢入宏任务队列,此时宏任务队列【set1.set2】:我将它命名为set2
        console.log('9');//第三轮宏任务执行,输出【9】
        process.nextTick(function() { //第三轮宏中执行过程中添加到微任务【10】
            console.log('10');
        })
        new Promise(function(resolve) {
            console.log('11');//第三轮宏任务执行,宏任务累计输出【9,11】
            resolve();
        }).then(function() {
            console.log('12') //第三轮宏中执行过程中添加到微任务【10,12】
        })
    })

    参考文章:

    这一次,彻底弄懂 JavaScript 执行机制

    10分钟理解JS引擎的执行机制

    从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理

    前端基础进阶(十二):深入核心,详解事件循环机制

  • 相关阅读:
    MySQL/MariaDB 版本选择
    Linux查看某个进程的磁盘IO读写情况 pidstat
    Oracle 11gR2 Database UNDO表空间使用率居高不下处理
    Linux十字病毒查杀处理
    MySQL字符集与校对
    点与线、线与线之间的位置关系
    [向量] 点积应用-两个向量夹角
    点与线的距离及垂足点
    unity 4.6.1脚本解析出错,没有激活的勾,方法顺序出错
    Error building Player: Exception: Could not start java
  • 原文地址:https://www.cnblogs.com/yaoyao-sun/p/10475689.html
Copyright © 2011-2022 走看看