-
事件循环机制由三部分组成:函数调用栈、微任务队列、消息队列
-
js:单线程,解释性语言
-
event-loop是js的执行机制
-
任务:同步任务:当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染;异步任务:像加载图片音乐之类占用资源大耗时久的任务。
-
event-loop开始的时候,会从全局一行一行的执行,遇到函数调用,会压入到函数调用栈中,被压入的函数被称之为帧,当函数返回后会从栈中弹出
-
宏任务:js中的异步操作:比如fetch,setTimeout,setInterval,script,setImmediate压入到函数调用栈中的时候里面的消息会进入到消息队列中去,消息队列会等到函数调用栈清空后再执行
function func1() { console.log(1) } function func2(){ setTimeout(() => { console.log(2) }, 0) func1() console.log(3) } func2() // func2压入栈中,接着setTimeout压入栈中,但是里面的消息console.log(2)会进入消息队列 // func1被压入栈中,等函数调用栈全部执行完之后菜会执行消息队列,因此执行顺序是1,3,2 -
微任务:微任务队列:原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver、async、await异步操作的时候会加入微任务队列中去,会在函数调用栈清空的时候立即执行,函数调用栈中加入的微任务会立马执行
var p = new Promise(resolve => { console.log(4) // 加入函数调用栈,立即执行 resolve(5) // 加入微任务队列,函数调用栈清空的时候才执行 }) // 函数调用栈中加入的微任务会立马执行,所以先输出4 function func1() { console.log(1) } function func2() { setTimeout(() => { console.log(2) }, 0) func1() console.log((3) p.then(resolve => { console.log(resolve) }) } func2() // func2加入函数调用栈,setTimeout加入消息队列,func1()加入函数调用栈 // p.then加入微任务队列,先于消息队列 // 输出 1 3 5 2console.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 // Promise.resolve.then是已经将内容放入微任务列表 -
过程
- 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入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并执行setTimeout(() => { task() },3000) sleep(10000000) // task()进入Event Table并注册,计时开始 // 执行sleep函数,很慢,非常慢,计时仍在继续 // 3秒到了,计时事件timeout完成,task()进入Event Queue // 但是sleep也太慢了吧,还没执行完,只好等着。 // sleep终于执行完了,task()终于从Event Queue进入了主线程执行。 // setTimeout这个函数,是经过指定时间后,把要执行的任务(本例中为task()) // 加入到Event Queue中,又因为是单线程任务要一个一个执行, // 如果前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于3秒。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对应的回调函数,立即执行。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。 // 发现了process1和then1两个微任务。 // 执行process1,输出6。 // 执行then1,输出8。 // 第二轮时间循环从setTimeout1宏任务开始 // 首先输出2。 // 接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。 // new Promise立即执行输出4, // then也分发到微任务Event Queue中,记为then2。 // 发现有process2和then2两个微任务可以执行。 // 输出3。 // 输出5。 // 第三轮事件循环开始,此时只剩setTimeout2了,执行 // 1 7 6 8 2 4 3 5 9 11 10 12