弄懂 JavaScript 执行机制event loop

一、EventLoop
javascript是一门单线程语言,既然是单线程的,就是同一时间只能做一件事情。那么问题来了,我们访问一个页面,这个页面的初始化代码运行时间很长,比如有很多图片、视频、外部资源等等,难道我们也要一直在那等着吗?答案当然是 不能

所以就出现了两类任务:同步任务和异步任务
在这里插入图片描述

1.同步和异步任务分别进入不同的 ‘‘场所’’ 执行。所有同步任务都在主线程上执行,形成一个执行栈;而异步任务进入Event Table并注册回调函数。
2.当这个异步任务有了运行结果,Event Table会将这个回调函数移入Event Queue,进入等待状态。
3.当主线程内同步任务执行完成,会去Event Queue读取对应的函数,并结束它的等待状态,进入主线程执行。
4.主线程不断重复上面3个步骤,也就是常说的Event Loop(事件循环)。

  • 那怎么知道主线程执行栈为空啊?

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

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

宏任务:整体代码script、setTimeout、setInterval、I/O、UI交互事件,可以理解是每次执行栈执行的代码就是一个宏任务。
微任务:Promise,process.nextTick,且process.nextTick优先级大于promise.then。可以理解是在当前 task 执行结束后立即执行的任务;

事件循环的顺序,决定js代码的执行顺序。进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
如下举例:

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

new Promise(function(resolve) {
    console.log('promise');
}).then(function() {
    console.log('then');
})

console.log('console');
  • 这段代码作为宏任务,进入主线程。
  • 先遇到setTimeout,那么将其回调函数注册后分发到宏任务Event Queue。
  • 接下来遇到了Promise,new Promise立即执行,then函数分发到微任务Event Queue。
  • 遇到console.log(),立即执行。
  • 整体代码script作为第一个宏任务执行结束,看看有哪些微任务?我们发现了then在微任务Event Queue里面,执行。
  • 第一轮事件循环结束了,我们开始第二轮循环,从宏任务Event Queue开始。我们发现了宏任务Event Queue中setTimeout对应的回调函数,立即执行。
  • 结束。
    二、下面来具体介绍一下宏任务和微任务

1、setTimeout
setTimeout的秒数意思是经过指定时间后,把要执行的任务加入到Event Queue中,又因为是单线程任务要一个一个执行,如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于指定的时间。举例说明:

setTimeout(() => {
	myFn()
},2000)
bigFn(10000000)
  • 遇到setTimeout,将myFn()函数注册到Event table中,
  • 执行bigFn(10000000)函数,假设这个函数很慢,仍在计时中
  • 2000ms过去了,要将myFn()放入到Event queue中,bigFn(10000000)函数依然没有执行完。等待…
  • bigFn(10000000)函数执行完毕,将myFn()从Event queue中拉出来放入到主线程中执行。

补充:setTimeout(fn,0)的意思是,当主线程空闲下来,立刻执行fn()
2、setInterval
对于setInterval(fn,ms)来说,不是每过ms会执行一次fn,而是每过ms,会有fn进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。
3、Promise

Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的,then后面的回调函数放在了微任务队列中。

举例说明:

new Promise(function(resolve) {
    console.log('10');
    resolve();
}).then(function() {
    console.log('11');
});
console.log('12');
// 10 12 11
  • new Promise方法体中的函数是被当做同步代码立即执行的,所以先输出10
  • then被分配到微任务队列中。
  • 遇到console.log('12');,执行。主线程清空,接下来执行微任务队列,输出11。

4、async和await
async函数返回的是一个Promise对象,所以它里面的方法体也是立即执行函数。
当async函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。也就是说,一旦遇到await,就让出了线程(跳出async函数体)然后继续执行后面的脚本的
举例:

async function async1(){
    console.log('1')
    await async2()
    console.log('2')
}
async function async2(){
    console.log('3')
}
console.log('4')
async1();
// 4  1 3 2
  • async为立即执行函数,所以输出1
  • 遇到await async2(),因为async2()也是async函数,所以立即执行,输出3,但是由于有await,所以让出线程,将async1()后面未执行的语句放入到微任务队列中,继续执行下面的函数。
  • 遇到console.log('4'),输出4,此时主线程空闲,将微任务中的任务拉到主线程中执行。
  • 输出2。
    资料参考:
    https://segmentfault.com/a/1190000019494012
    https://juejin.cn/post/6844903512845860872#heading-5
    https://segmentfault.com/a/1190000015057278
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值