天天看点

javascript --- > event loop栗子1JS的执行顺序常见的宏任务和微任务栗子1说明:栗子2浏览器中的事件循环Node.js的Event Loop过程:setTimeout 和 setImmediate栗子3

栗子1

  • 求下面函数的输出
console.log('script start');

setTimeout(() => {
  console.log('setTimeoout');
}, 0);

Promise.resolve().then(function(){
  console.log('promise1');
}).then(function(){
  console.log('promise2');
})
console.log('script end');
           
javascript --- > event loop栗子1JS的执行顺序常见的宏任务和微任务栗子1说明:栗子2浏览器中的事件循环Node.js的Event Loop过程:setTimeout 和 setImmediate栗子3
  • 说明: 在"promise2"和"setTimeoout"之间有"<· undefined"
  • “<· undefined”: 其实是进入了下一轮事件循环
  • 可视化展示: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

JS的执行顺序

  • 原则:
  1. 事件循环过程中,每次只执行1个宏任务
  2. 在执行宏任务之前,首先检查微任务队列是否为空.若存在微任务,则先执行微任务

常见的宏任务和微任务

  • 常见的宏任务: setTimeout、setInterval、setImmediate(Node)、requestAnimationFrame(浏览器)、I/O、UI rendering(浏览器)
  • 常见的微任务: process.nextTick(Node)、promise.then()、Obeject.observe、MutationObeserve

栗子1说明:

  • 在了解了执行顺序以及宏/微任务之后,再看上面的栗子1:
  • console.log('script start')

    : 同步任务, 输出 ‘script start’
  • setTimeout(function(){ console.log('setTimeout') },0)

    : 这是一个宏任务,会将函数

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

    推到宏任务队列中
  • Promise.resovle().then(function(){console.log('promise1')}).then(function(){console.log('promise2')})

    : 2个微任务,一次推入微任务队列
  • console.log('script end')

    : 同步任务,输出 ‘script end’
  • 到了这里,开始执行事件循环的下一轮,根据原则2.先检擦微任务队列是否为空,此时不为空,于是执行队列的第一个(即输出 ‘promise1’),然后出队,在检查微任务队列是否为空(此处不为空,故输出’promise2’,出队),在检查…
  • 当微任务队列为空,代表当前事件循环结束,所以会输出一个返回值,此处未设定,故输出"<· undefined"
  • 从宏任务队列中读取,输出(“setTimeout”)

栗子2

  • 求以下函数的输出结果
new Promise(resolve => {
        console.log("resolve")
        resolve()
    })
    .then(() => console.log("promise then..."))

setImmediate(() => {
    console.log("set immediate...")
})

setTimeout(() => {
    console.log("set Timeout ...");
}, 0);

process.nextTick(() => {
    console.log("nextTick")
})
           
javascript --- &gt; event loop栗子1JS的执行顺序常见的宏任务和微任务栗子1说明:栗子2浏览器中的事件循环Node.js的Event Loop过程:setTimeout 和 setImmediate栗子3
  • 说明:
  1. Promise是宏任务,其里面的函数是同步的.即会执行

    console.log('resolve')

  2. nextTick可以理解为在其他类型微任务的前面入队.

浏览器中的事件循环

  • 执行全局Script的同步代码
  • 执行microtask任务
  • 从宏任务队列中取出队首一个任务
  • 执行该任务
  • 任务执行完毕,检查是否有微任务(有则执行,否则执行第一步)

Node.js的Event Loop过程:

  1. 执行全局Script的同步代码
  2. 执行microtask微任务,先执行所有 Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  3. 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,六个阶段: Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue…
  • MacroTask包括: setTimeout、setInterval、setImmediate(Node)、requestAnimation(浏览器)、IO、UI rendering(浏览器)
  • MicroTask包括:s process.nextTick(Node)、Promise.then、Object.observe、MutationObserver

setTimeout 和 setImmediate

  • setImmediate():方法用于中断长时间运行的操作,并在完成其他操作后立即运行回调函数
  • 栗子:
setTimeout(() => {
    console.log('setTimeout');
}, 0);

setImmediate(() => {
    console.log('setImmediate');
})
           
javascript --- &gt; event loop栗子1JS的执行顺序常见的宏任务和微任务栗子1说明:栗子2浏览器中的事件循环Node.js的Event Loop过程:setTimeout 和 setImmediate栗子3

同样的代码执行的结果不确定:

  • setTimeout/setInterval的第二个参数取值范围是: [1, 2^31 -1],如果超过这个范围就会初始化为1,即 setTimeout(fn, 0) === setTimeout(fn, 1);
  • setTimeout的回调函数再timer阶段执行,setImmediate的回调函数再check阶段执行,event loop的开始会检查timer阶段,但是再开始之前到timer阶段会消耗一定时间,就会出现以下情况:
  1. timer前的准备时间超过1ms, 满足loop -> time >=1, 则执行timer阶段(setTimeout)的回调函数
  2. timer前的准备时间小于1ms,则先执行check阶段(setImmediate)的回调函数,下一次event loop执行timer阶段(setTimeout)的回调函数

栗子3

console.time("start");
setImmediate(function() {
    console.log(1);
});
setTimeout(function() {
    console.log(2);
}, 10);
new Promise(function(resolve) {
    console.log(3);
    resolve();
    console.log(4);
}).then(function() {
    console.log(5);
    console.timeEnd("start")
});
console.log(6);
process.nextTick(function() {
    console.log(7);
});
console.log(8);
           
javascript --- &gt; event loop栗子1JS的执行顺序常见的宏任务和微任务栗子1说明:栗子2浏览器中的事件循环Node.js的Event Loop过程:setTimeout 和 setImmediate栗子3
  • 说明:
  1. 首先执行script代码,输出3468
  2. 执行nextTick任务,输出7
  3. 执行microtask, 输出5, start: 15.232ms
  4. 如果事件大于10ms,则执行宏任务 “输出2”, 否则输出"1"

继续阅读