js的执行机制--EventLoop

643 阅读4分钟

js的执行机制--EventLoop

js是一门 单线程 语言,一切js的多线程都是用单线程模拟出来的

单线程任务

同步任务 异步任务

任务队列.png

  1. 同步任务进入主线程异步任务进入Event Table并注册函数
  2. 当指定事件完成时,Event Table会将这个函数移入Event Queue
  3. 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数进入主线程执行
  4. 重复上述过程,即Event Loop(事件循环)

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

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

异步任务

宏任务 微任务 不同类型的任务会进入对应的Event Queue

事件循环、宏任务、微任务关系.png

  1. 事件循环的顺序,决定js代码的执行顺序
  2. 进入整体代码(宏任务)后,开始第一次循环
  3. 接着执行所有的微任务
  4. 再次执行宏任务,找到其中一个任务队列执行完毕
  5. 再执行所有微任务

2.1 宏任务macro-task

  • 整体代码script(UIrender)
  • setTimeout
  • setInterval
  • setImmediate
  • i/o

setTimeout

setTimeout(() => {
  task()
  console.log('延时3s')
}, 3000)
定义延时3s,实际却5、6s才执行函数,是怎么回事?

理解setTimeout的定义

  1. task()进入Event Table并注册,计时开始
  2. 执行sleep函数,计时仍在继续
  3. 3s到了,计时事件timeout完成,task()进入Event Queue,但是sleep还没执行完,事件等待
  4. sleep执行完,task()Event Queue进入主线程执行

setTimeout是经过指定时间后,把要执行的任务加入到Event Queue中,又因为单线程任务要一个一个执行,如果前面的任务需要的时间太久,就只能等待,导致真正的延迟时间大于3s

零延迟
  • 零延迟(Zero delay)并不意味着立即执行

    • 在零延迟调用setTimeout时,并不是过了给定时间间隔就马上执行回调函数
    • 其等待的时间基于队列正在等待的消息数量

setTimeout()只是将事件插入了任务队列,必须等到当前任务(执行栈)执行完,主线程才会去执行指定的回调函数。没办法保证回调函数一定会在setTimeout()指定的时间执行

总结
  • setTimeout(fn, 0)的含义是: 指定任务在主线程最早可得的空闲时间执行。也就是说,尽可能早的执行。它在任务队列的尾部添加一个事件,因此要等到主线程把同步任务和任务队列现有的事件都处理完,才会得到执行
  • 在某种程度上,我们可以利用setTimeout(fn,0)的特性,修正浏览器的任务顺序

setInterval

  • 对于执行顺序来说,setInterval会隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待

一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出有时间间隔了

2.2 微任务micro-task

  • Promise
  • process.nextTick
  • mutationObserver

nextTick

nextTick的由来
  • 由于vue的数据驱动视图更新是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
nextTick的触发时机
  • 在同一事件循环中的数据变化后,DOM完成更新,立即执行nextTick(callback)内的回调。
应用场景
  • 需要在视图更新之后,基于新的视图进行操作。
简单总结事件循环
  • 同步代码执行 -> 查找异步队列,推入执行栈,执行callback1[事件循环1] ->查找异步队列,推入执行栈,执行callback2[事件循环2]...
  • 每个异步callback,最终都会形成自己独立的一个事件循环。
结合nextTick的由来,可以推出每个事件循环中,nextTick触发的时机
  • 同一事件循环中的代码执行完毕 -> DOM 更新 -> nextTick callback触发

总结

  • 宏队列:执行每个宏队列时会先清空微任务队列
  • 微队列:微队列为空时执行宏队列

任务队列中,在每一次事件循环中,macrotask只会提取一个执行,而microtask会一直提取,直到microtask队列为空。

栗子

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

js执行机制.png