JS原理 -事件循环 Event Loop

JS 中的事件循环及相关实例

定义

JS 在设计之初是被用来验证表单和操控DOM元素,如果js是多线程的,且两个线程同时对一个DOM元素进行了相互冲突的操作,那么浏览器的解析器是无法执行的,所以 JS 被设计为单线程。

Event Loop 即事件循环,是浏览器、Nodejs 避免因单线程运行出现阻塞的一种机制,也就是说通常说的异步。

宏队列和微队列

宏队列(macro task queue)以下异步任务的回调会依次进入宏队列,等待后续被调用:

  • setTimeout
  • setInterval
  • I/O(文件读写)
  • requestAnimationFrame (浏览器独有)
  • UI rendering (浏览器独有)

微队列(micro task queue)以下异步任务的回调会依次进入微队列,等待后续被调用:

  • Promise
  • MutationObserver
  • process.nextTick (Node独有)
  • Object.observe(ES7废弃)

简要流程

事件循环简要的流程如下:

  1. 执行全局 script 代码,如果遇到宏任务,追加到宏队列中,如果遇到微任务,追加至微队列中。
  2. 全局代码执行完毕,调用栈清空。
  3. 检查微队列是否为空,如果不为空,依次取出微队列内的回调任务放入调用栈中,直到执行完成所有微任务,此时微队列为空,调用栈为空。
  4. 取出宏队列队首的任务,放入调用栈,执行完毕后,重复步骤 3 的操作,Event Loop 就形成了。

流程的重点就在于:每一次调用宏队列里的任务后,都需要去检查微队列是否为空,如果有微任务则执行微任务。

例子

console.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');

事件循环详细过程:

  1. 执行全局 script 代码,分配任务到队列。

    宏队列:setTimeout callback

    微队列:Promise then

    Log:script start、script end

  2. 此时微队列不为空,需要处理微队列。

    宏队列:setTimeout callback

    微队列:

    Log:script start、script end、promise1、promise2

  3. 微队列处理完成之后,此时微队列已空,然后再处理宏队列里的 setTimeout callback 任务。

    宏队列:

    微队列:

    Log:script start、script end、promise1、promise2、setTimeout

复杂的例子

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3)
  });
});

new Promise((resolve, reject) => {
  console.log(4)
  resolve(5)
}).then((data) => {
  console.log(data);
})

setTimeout(() => {
  console.log(6);
},1000)

setTimeout(() => {
  console.log(7);
},100)

console.log(8);

事件循环详细过程:

  1. 执行全局 script 代码,因为 Promise 构造函数执行时立即调用,所以console.log(4)是同步的。

    宏队列:setTimeout callback、setTimeout callback、setTimeout callback

    微队列:Promise then

    Log:1、4、8

  2. 此时微队列不为空,需要处理微队列。

    宏队列:setTimeout callback、setTimeout callback、setTimeout callback

    微队列:

    Log:1、4、8、5

  3. 处理 setTimeout callback,又因为 setTimeout 内包含了一个 Promise 函数,所以执行了 setTimeout 的 callback 时,微队列又添加了一个 Promise 的 callback。

    宏队列:setTimeout callback、setTimeout callback、setTimeout callback

    微队列:Promise then

    Log:1、4、8、5、2

  4. 完成宏队列里的一个任务后,判断微队列不为空,暂停处理宏队列,再次优先处理微队列。

    宏队列:setTimeout callback、setTimeout callback、setTimeout callback

    微队列:

    Log:1、4、8、5、2、3

  5. 依次处理剩下的宏队列任务。

    Log:1、4、8、5、2、3、7、6

async 乱入

async 的特性:

  1. async 定义的是一个 Promise 函数,和普通函数一样只要不调用就不会进入事件队列。

    async function async1(){
      console.log(1)
      await 1
      console.log(2)
    }
    // 等价于
    function async1() {
      console.log(1)
      return new Promise(resolve => {
        resolve(1)
      }).then(() => {
        console.log(2)
      })
    }
    // 小试一下
    async function async1 () {
        await new Promise((resolve, reject) => {
            resolve()
        })
        console.log('A')
    }
    async1()
    new Promise((resolve) => {
        console.log('B')
        resolve()
    }).then(() => {
        console.log('C')
    }).then(() => {
        console.log('D')
    })
    // 打印结果:B、A、C、D
    
  2. async 内部如果没有主动 return Promise,那么 async 会把函数的返回值用 Promise 包装。

    根据返回是否有then属性,是否是Promise,Js引擎的处理方式也不一样:

    • 非 thenable、非 promise(不等待)

      async function testA () {
          return 1;
      }
      testA().then(() => console.log(1));
      Promise.resolve()
          .then(() => console.log(2))
          .then(() => console.log(3));
      // 输出结果:1/2/3
      
    • thenable(等待 1个then的时间)

      async function testB () {
          return {
              then (cb) {
                  cb();
              }
          };
      }
      testB().then(() => console.log(1));
      Promise.resolve()
          .then(() => console.log(2))
          .then(() => console.log(3));
      // 输出结果:2/1/3
      
    • Promise(等待 2个then的时间)

      async function testC () {
          return new Promise((resolve, reject) => {
              resolve()
          })
      }
      
      testC().then(() => console.log(1));
      Promise.resolve()
          .then(() => console.log(2))
          .then(() => console.log(3))
              .then(() => console.log(4))
      // 输出结果:2/3/1/4
      
  3. await 表达式会暂停整个 async 函数的执行进程并出让其控制权,只有当其等待的基于 promise 的异步操作被兑现或被拒绝之后才会恢复进程。即:遇到 await 关键字,await 右边的语句会被立即执行然后 await 下面的代码进入等待状态,等待 await 得到结果。

    但是执行顺序和右侧函数的类型有关:

    • 非 thenable、非 Promise(不等待)

      async function test () {
          console.log(1);
          await 1;
          console.log(2);
      }
      test();
      console.log(3);
      Promise.resolve()
          .then(() => console.log(4))
          .then(() => console.log(5))
          .then(() => console.log(6))
          .then(() => console.log(7));
      // 输出结果: 1/3/2/4/5/6/7
      
    • thenable(等待 1个then的时间)

      async function test () {
          console.log(1);
          await {
              then (cb) {
                  cb();
              },
          };
          console.log(2);
      }
      test();
      console.log(3);
      Promise.resolve()
          .then(() => console.log(4))
          .then(() => console.log(5))
          .then(() => console.log(6))
          .then(() => console.log(7));
      // 输出结果: 1/3/4/2/5/6/7
      
    • Promise(不等待)

      async function test () {
          console.log(1);
          await new Promise((resolve, reject) => {
              resolve()
          })
          console.log(2);
      }
      test();
      console.log(3);
      Promise.resolve()
          .then(() => console.log(4))
          .then(() => console.log(5))
          .then(() => console.log(6))
          .then(() => console.log(7));
      // 输出结果: 1/3/2/4/5/6/7
      
      async function async1() {
        console.log('1')
        await new Promise((resolve) => {
          console.log('2')
          resolve()
        }).then(() => {
          console.log('3')
        })
        console.log('4')
      }
      async1()
      
      // 输出结果: 1/2/3/4
      

来几个 async/await 的例子:

async function async1() {
    console.log( '1' );
    await async2();
    console.log( '2' );
};

async function async2() {
    console.log( '3' );
};

console.log( '4' );

setTimeout( function () {
    console.log( '5' );
}, 0 );

async1();

new Promise( function ( resolve ) {
    console.log( '6' );
    resolve();
} ).then( function () {
    console.log( '7' );
} );

console.log( '8' );

// 事件循环的详细顺序:
1. 执行同步代码 console.log。
   Log:4
2. 将 setTimeout 添加至宏队列。
3. 执行 async1。
   Log:41
4. 执行 async2,await 阻塞后续代码,先执行外部的代码,又因为 async 会返回一个 Promise,所以将返回的 Promise 添加至微队列。
   Log:413
5. 执行 Promise,将新生成的 Promise 添加至微队列。
   Log:4136
6. 执行同步代码 console.log。
   Log:41368
7. 查看微队列,发现存在两个 Promise,按顺序执行
   Log:4136827
8. 最后执行宏任务里的 setTimeout
   Log:41368275
setTimeout(function () {
  console.log('9')
}, 0)
console.log('1')
async function async1() {
  console.log('2')
  await async2()
  console.log('8')
}
async function async2() {
  return new Promise(function (resolve) {
    console.log('3')
    resolve()
  }).then(function () {
    console.log('6')
  })
}
async1()

new Promise(function (resolve) {
  console.log('4')
  resolve()
}).then(function () {
  console.log('7')
}).then(function () {
  console.log('9')
})
console.log('5')

// 输出结果:1/2/3/4/5/6/7/8/9
// 重点:async2返回的是Promise,所以等待两个then(6/7)
async function async1() {
  console.log('2')
  const data = await async2()
  console.log(data)
  console.log('9')
}

async function async2() {
  return new Promise(function (resolve) {
    console.log('3')
    resolve('8')
  }).then(function (data) {
    console.log('6')
    return data
  })
}
console.log('1')

setTimeout(function () {
  console.log('10')
}, 0)

async1()

new Promise(function (resolve) {
  console.log('4')
  resolve()
}).then(function () {
  console.log('7')
})
console.log('5')

// 输出结果:1/2/3/4/5/6/7/8/9/10

JS原理 -事件循环 Event Loop

https://hashencode.github.io/post/11477405/

作者

BiteByte

发布于

2020-08-08

更新于

2024-11-15

许可协议