EventLoop

EventLoop

eventLoop 是由 JS 的宿主环境(浏览器)来实现的;事件循环可以简单的描述为以下四个步骤:

/*
1. 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;
2. 此期间WebAPIs完成这个事件,把回调函数放入队列中等待执行(微任务放到微任务队列,宏任务放到宏任务队列)
3. 执行栈为空时,Event Loop把微任务队列执行清空;
4. 微任务队列清空后,进入宏任务队列,取队列的第一项任务放入Stack(栈)中执行,回到第1步。

我们不禁要问了,那怎么知道主线程执行栈为空呢?js 引擎存在 monitoring process 进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去 Event Queue 那里检查是否有等待被调用的函数

js 是单线程,就像学生排队上厕所,学生需要排队一个一个上厕所,同理 js 任务也要一个一个顺序执行。如果一个任务耗时过长,那么后一个任务也必须等着。那么问题来了,假如我们想浏览新闻,但是新闻包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?因此聪明的程序员将任务分为两类:

同步任务

当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,但本文的目的是用最小的学习成本彻底弄懂执行机制

异步任务

异步任务又分为宏任务和微任务。微任务:then 、messageChannel 、mutationObersve;宏任务:setTimeout、setInterval、setTmmediate(只兼容 ie)。

js 异步有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入 eventqueue,然后在执行微任务,将微任务放入 eventqueue 最骚的是,这两个 queue 不是一个 queue。当你往外拿的时候先从微任务里拿这个回掉函数,然后再从宏任务的 queue 上拿宏任务的回掉函数。 我当时看到这我就服了还有这种骚操作。

微任务(Microtasks)

then 、messageChannel 、mutationObersve

  • Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。

setTimeout(() => {
  console.log("setTimeout1");
}, 0);
let p = new Promise((resolve, reject) => {
  console.log("Promise1");
  resolve();
});
p.then(() => {
  console.log("Promise2");
});
// Promise1->Promise2->setTimeout1

Promise 参数中的 Promise1 是同步执行的 其次是因为 Promise 是 microtasks,会在同步任务执行完后会去清空 microtasks queues, 最后清空完微任务再去宏任务队列取值。

  • process.nextTick(callback)

process.nextTick(callback)类似 node.js 版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

async function async1() {
  console.log("async1 start");
  await async2();
  console.log("async1 end");
}
async function async2() {
  console.log("async2");
}
console.log("script start");
setTimeout(function() {
  console.log("settimeout");
});
async1();
new Promise(function(resolve) {
  console.log("promise1");
  resolve();
}).then(function() {
  console.log("promise2");
});
setImmediate(() => {
  console.log("setImmediate");
});
process.nextTick(() => {
  console.log("process");
});
console.log("script end");
// script start->async1 start->async2->promise1->script end->async1 end->process->promise2->settimeout->setImmediate

process.nextTick 会在微任务将执行时执行,所以会比.then 先一步执行。

宏任务(task)

包括整体代码 setTimeout、setInterval、setTmmediate(只兼容 ie)。

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

乍一看其实差不多嘛,但我们把这段代码在 chrome 执行一下,却发现控制台执行 task()需要的时间远远超过 3 秒,说好的延时三秒,为啥现在需要这么长时间啊? 这时候我们需要重新理解 setTimeout 的定义。我们先说上述代码是怎么执行的:

/*
task()进入Event Table并注册,计时开始。
执行sleep函数,很慢,非常慢,计时仍在继续。
3秒到了,计时事件timeout完成,task()进入Event Queue,但是sleep也太慢了吧,还没执行完,只好等着。
sleep终于执行完了,task()终于从Event Queue进入了主线程执行。
  • setTimeout(fn,0)

我们还经常遇到 setTimeout(fn,0)这样的代码,0 秒后执行又是什么意思呢?是不是可以立即执行呢?

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

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

  • setInterval

上面说完了 setTimeout,当然不能错过它的孪生兄弟 setInterval。他俩差不多,只不过后者是循环的执行。对于执行顺序来说,setInterval 会每隔指定的时间将注册的函数置入 Event Queue,如果前面的任务耗时太久,那么同样需要等待。

唯一需要注意的一点是,对于 setInterval(fn,ms)来说,我们已经知道不是每过 ms 秒会执行一次 fn,而是每过 ms 秒,会有 fn 进入 Event Queue。一旦 setInterval 的回调函数 fn 执行时间超过了延迟时间 ms,那么就完全看不出来有时间间隔了。这句话请读者仔细品味。

Node 的 EventLoop

Node 环境中微任务是插缝执行,(如果执行宏任务的时候发现了微任务, 不会像浏览器一样执行了,而是将为微任务放到微任务队列中,等待整个宏 任务队列执行完毕或者达到执行上线后,下一个阶段开始的时候先执行 完微任务队列中的任务)。

  • 认识 Node 中的任务源(task)
/*
微任务: then 、nextTick 、 messageChannel 、mutationObersve
宏任务:setTimeout 、setInterval 、setImmediate 、io 文件操作

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值