通过同步异步、宏任务微任务、事件循环机制来判断 setTimeout和Promise执行顺序

191 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

单线程

JavaScript是一门单线程语言,即同一时间段只能执行一件事,单线程意味着如果同个时间有多个任务的话,这些任务就需要排队等待,上一个任务执行完才会执行下一个任务。

同步任务跟异步任务

为什么要分同步与异步

由于js同一时间只能执行一个任务,多个任务需要进行排队。假如当前的任务执行时间较长,比如ajax获取数据,用户就需要等待数据回来才能进行下一步,这个时间段用户不能进行任何操作,严重影响用户体验。

js是单线程的,但又可同时进行异步操作,这两者不是完全相反的吗?js是单线程模式,但是js的宿主浏览器不是单线程。

同步

同步任务就是指主线程上排队的任务,当一个任务执行完成才会继续执行下一个任务。

异步

异步任务就是指不进入主线程,而进入任务队列的任务。这个队列的所有任务进入主线程执行,而是进入浏览器提供的线程执行,执行完成之后会产生一个回调函数,并通知主线程。主线程执行完当前任务就会调取最早通知的主线程的回调函数,使其进入主线程中执行。

宏微任务

宏任务

参与了事件循环的任务。 回到 Chromium 中,需要处理的消息主要分成了三类:

  • Chromium 自定义消息
  • Socket 或者文件等 IO 消息
  • UI 相关的消息
  1. 与平台无关的消息,例如 setTimeout 的定时器就是属于这个
  2. Chromium 的 IO 操作是基于 libevent 实现,它本身也是一个事件驱动的库
  3. UI 相关的其实属于 blink 渲染引擎过来的消息,例如各种 DOM 的事件

其实与 JavaScript 的引擎无关,都是在 Chromium 实现的。

微任务

直接在 Javascript 引擎中的执行的,没有参与事件循环的任务。

  1. new Promise().then 回调
  2. MutationObserver,监控dom节点变化;MutationObserver使用“异步”+“微任务”的方式,替代旧版mutation event这个同步事件,异步解决同步操作的性能问题,微任务解决了实时性的问题;
  3. Object.observe,已经废弃了,用Proxy对象替代;

event loop 事件循环

  1. 浏览器的事件循环,是在渲染进程中的。
  2. 执行一个宏任务,栈中没有就从事件队列中获取。
  3. 执行过程中如果遇到微任务,就添加到微任务的队列中。
  4. 当前这个宏任务执行完毕后,立即执行当前微任务队列的所有微任务。
  5. 当前宏任务执行完毕,GUI线程接管渲染。
  6. 渲染完毕后,JS线程继续接管,开始下一个宏任务。 简化就两步:
    执行一个宏任务,执行完它对应的所有微任务;

小试牛刀

使用同步异步、宏微任务以及事件循环,判断以下函数的执行顺序

    async function async1() {
        console.log("async1 start");
        await async2();
        console.log("async1 end");
    }
    async function async2() {
        console.log("async2");
    }
    async1();
    setTimeout(() => {
        console.log("timeout");
    }, 0);
    new Promise(function (resolve) {
        console.log("promise1");
        resolve();
    }).then(function () {
        console.log("promise2");
    });
    console.log("script end");
//正确答案是
console.log("async1 start");
console.log("async2");
console.log("promise1");
console.log("script end");
console.log("async1 end");
console.log("promise2");
console.log("timeout");

嘿嘿上面的搞明白了,现在看下这个升级版的

console.log('global')

for (var i = 1;i <= 5;i ++) {
    setTimeout(function() {
        console.log('set:',i)
    },i*1000)
    console.log('i:',i)
}

new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
     console.log('then1')
})

setTimeout(function () {
    console.log('timeout2')
    new Promise(function (resolve) {
        console.log('timeout2_promise')
        resolve()
    }).then(function () {
        console.log('timeout2_then')
    })
}, 1000)

正确答案是:

i: 1
VM12611:5 i: 2
VM12611:5 i: 3
VM12611:5 i: 4
VM12611:5 i: 5
VM12611:9 promise1
VM12611:12 then1
597
VM12611:3 set: 6
VM12611:16 timeout2
VM12611:18 timeout2_promise
VM12611:21 timeout2_then
4VM12611:3 set: 6