• 欢迎大家分享资料!前往留言板评论即可!

js事件循环机制之宏任务与微任务

合宙 模组资料网 2年前 (2021-05-15) 243次浏览 已收录 0个评论 扫描二维码
  • JavaScript是单线程的语言
  • Event Loop是JavaScript的执行机制

js事件循环

js是单线程,所以js任务也要一个一个按顺序执行。如果一个任务耗时过长,那么其后的任务必须等着。这就会导致图片加载,请求数据会让降低线程效率。所以js中将任务分为了两类:

  • 同步任务
  • 异步任务

当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,这里就不再赘述。

如下一段代码:

console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

执行结果:

script start

script end,

promise1,

promise2,

setTimeout

为什么会出现这样打印顺序呢?

js事件循环机制之宏任务与微任务

图片来源于网络

解读:

  • 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

以下代码的执行流程如下:

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并执行

微任务(Micro tasks)与宏任务(task)

微任务和宏任务皆为异步任务,它们都属于一个队列;主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢?

js事件循环机制之宏任务与微任务

图片来源于网络

宏任务一般是:包括整体代码script,setTimeout,setInterval、setImmediate。

微任务:原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver 记住就行了。

setTimeout

大名鼎鼎的setTimeout无需再多言,大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行:

setTimeout(() => {
    console.log('延时3秒');
},3000)

但是有时候也会出现写的延时3秒,实际却5,6秒才执行函数的情况。代码如下:

setTimeout(() => {
   test()
},3000)

function test(){
    console.log("123")
}
// 概念代码,模拟同步代码执行速度慢
sleep(10000000)
  • task()进入Event Table并注册,计时开始。

  • 执行sleep函数,很慢,定时器仍在计时。

  • 3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep还未执行完成。

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

我们还经常遇到setTimeout(fn,0)这样的代码,这里的0秒并不代表能立即执行,它的的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

//代码1
console.log('先执行这里');
setTimeout(() => {
    console.log('执行啦')
},0);

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

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

setInterval

setInterval与setTimeout差不多,只不过setInterval是循环执行的。对于执行循序而言,setInterval会每隔特定时间将注册的函数放入Event Queue,如果前面的任务执行时间过长,同样需要等待。

Promise与process.nextTick(callback)

  • Promise的定义和功能本文不再赘述,可以学习一下 阮一峰老师的Promise
  • 而process.nextTick(callback)类似node.js版的”setTimeout”,在事件循环的下一次循环中调用 callback 回调函数

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

例子

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')
    })
})

第一轮事件循环流程分析如下:

  • 整体script作为第一个宏任务进入主线程,遇到console.log,输出1
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1
  • 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1
  • 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2
宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1
  • 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了17

我们发现了process1和then1两个微任务。

  • 执行process1,输出6
  • 执行then1,输出8

好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:

  • 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。
  • new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2
宏任务Event Queue 微任务Event Queue
setTimeout2 process3
then3
  • 第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
  • 输出10
  • 输出12
  • 第三轮事件循环结束,第三轮输出9,11,10,12
  • 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

希望大家看了本篇文章都有收获 …


转载请注明原文链接:js事件循环机制之宏任务与微任务
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址