JS的执行顺序你都搞懂了吗?

125 阅读2分钟

为了彻底弄懂JS执行顺序,网上找来了一些经典案例和图解,但不知道资源的最终来源,所以未注明出处,如有涉及版权问题,请及时联系

先来做一套测试题

//1.
for (var i = 0; i < 5; i++) {
  console.log(i);
}
//0 1 2 3 4
//2.
for (var i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000 * i);
}
//5 5 5 5 5
//3.
for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
//0 1 2 3 4
//4.
for (var i = 0; i < 5; i++) {
  (function() {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}
//5 5 5 5 5
//5.
for (var i = 0; i < 5; i++) {
  setTimeout((function(i) {
    console.log(i);
  })(i), i * 1000);
}
//0 1 2 3 4
//6.
setTimeout(function() {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for( var i=0 ; i<10000 ; i++ ) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function() {
  console.log(4);
});
console.log(5);
//2 3 5 4 1
//7.
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');
}, 0)
async1();
new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

JS事件循环(Event Loop)

  1. JS是单线程的,所有任务都在主线程中执行
  2. 任务分为同步任务异步任务
  3. 同步任务先进入主线程排队执行,排队的地方叫:执行栈
  4. 异步任务先挂起,有返回结果或回调时,加入:任务队列
  5. 主线程任务全部执行完毕后,领取任务队列里的任务进入主线程继续执行
  6. 异步任务分为两种:宏任务(macrotask)微任务(microtask),执行任务的顺序:先 微任务宏任务
  7. 宏任务:
    • setTimeOut
    • setInterval
    • setImmediate(Node)
    • requestAnimationFrame(浏览器)
    • run
    • I/O
    • UI rendering 微任务:
    • process.nextTick(Node)
    • MutationObserver(浏览器)
    • Promise的 .then .catch .finally

image.png

image.png

案例分析

// node环境

console.log('1');
setTimeout(function() {//setTimeout1
    console.log('2');
    process.nextTick(function() {//process2
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {//then2
        console.log('5')
    })
})
process.nextTick(function() {//process1
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {{//then1
    console.log('8');
})

setTimeout(function() {//setTimeout2
     console.log('9');
    process.nextTick(function() {//process3
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {//then3
        console.log('12');
    })
})

第一轮循环:

  1. 首先打印 1
  2. 接下来是setTimeout是异步任务且是宏任务,加入宏任务暂且记为 setTimeout1
  3. 接下来是 process 微任务 加入微任务队列 记为 process1
  4. 接下来是 new Promise 里面直接 resolve(7) 所以打印 7 后面的then是微任务 记为 then1
  5. 最后是 setTimeout 宏任务 记为 setTimeout2 第一轮循环打印出的是 1 7
  • 当前宏任务队列:setTimeout1, setTimeout2
  • 当前微任务队列:process1, then1,

第二轮循环:

  1. 执行所有微任务
  2. 执行process1,打印出 6
  3. 执行then1 打印出8
  4. 微任务都执行结束了,开始执行第一个宏任务
  5. 执行 setTimeout1 也就是 第 3 - 14 行
  6. 首先打印出 2
  7. 遇到 process 微任务 记为 process2
  8. new Promise中resolve 打印出 4
  9. then 微任务 记为 then2

第二轮循环结束,当前打印出来的是 1 7 6 8 2 4

  • 当前宏任务队列:setTimeout2
  • 当前微任务队列:process2, then2

第三轮循环:

  1. 执行所有的微任务
  2. 执行 process2 打印出 3
  3. 执行 then2 打印出 5
  4. 执行第一个宏任务,也就是执行 setTimeout2 对应代码中的 25 - 36 行
  5. 首先打印出 9
  6. process 微任务 记为 process3
  7. new Promise执行resolve 打印出 11
  8. then 微任务 记为 then3

第三轮循环结束,当前打印顺序为:1 7 6 8 2 4 3 5 9 11

  • 当前宏任务队列为空
  • 当前微任务队列:process3,then3

第四轮循环:

  1. 执行所有的微任务
  2. 执行 process3 打印出 10
  3. 执行 then3 打印出 12

第四轮循环结束,最终打印顺序为:1 7 6 8 2 4 3 5 9 11 10 12