1

事件循环

了解知识点

  • 线程
  • 执行栈
  • task queue
  • web api
  • macro task
  • micro task

线程

javascript是单线程的语言,
可以单线程将理解为只有一条车道,在车道里后面的车在等前面的车通过后,才能通过.
即当前面的程序没有执行,后面的程序也不能执行.

执行栈

执行栈像"车道",被执行的程序会放入执行栈里,
但它的执行的顺序是,后面进来的程序先执行.

示例

视频源地址

task queue

在线程中有很多等待运行的任务(程序,而执行栈只会放入一个任务.
其他可运行任务会放入任务队列中.
这里虽说是个队列, 它的执行的顺序,不会先进的程序先执行.
每个event loop都会有一个或多个任务队列

web api

javascript是单线程,但也能实现异步,这种实现基与页面提供很多API,如(setTimeout, ajax, addEventListener ...)
这些都是异步函数,也就是说,运行到异步函数时,
把异步函数里闭包放入web api里,等待正确的时机,
web api会把闭包放入task queue里执行.

示例

图片描述

macro task

macro task有setTimeout ,setInterval, setImmediate,requestAnimationFrame,I/O ,UI渲染...
task queue 是由很多个macro task组成的队列,

micro task

micro task有Promise, process.nextTick, Object.observe, MutationObserver...
每个event loop都会有一个micro task

event loop

执行流程


  1. 当执行栈为null时
  2. 看task queue的第一个macro task是不是null,如果不是取出放入执行栈,如果是跳转5
  3. 执行栈运行task
  4. 运行完毕,把task设置null,并移出
  5. 执行 micro task队列
    a. 看micro task队列第一task个是不是null,如果不是取出放入执行栈,如果是跳转f
    b. 执行栈运行task
    c. 运行完毕,把task设置null,并移出
    d. 看micro task队列下一个task是不是null,如果不是跳转b
    f. 结束micro task队列执行
  6. 跳转到1

示例

图片描述

实战

<script>// 创建macro task, 因为stack 为null, 执行
(function test() {
  setTimeout(() => { // 创建macro task, 当前task queue['第一setTimeout闭包']
    console.log(4)
  }, 0);
  new Promise(resolve => {
    console.log(1); // 执行, 控制台 1
    for(var i = 0; i < 10000; i++) {
      i == 9999 && resolve(); // 创建micro task, 当前micro task queue['第一then闭包']
    }
    console.log(2); // 执行, 控制台 1 2
  }).then(() => {
    console.log(5);
    return new Promise(resolve => {
        resolve()
    })
  }).then(() => {
      console.log(6)
  });
  console.log(3); /* 执行, 控制台 1 2 3, 第一个script标签创建的macro task结束
                     运行micro task queue首个micro task === 第一then闭包
                       执行, 控制台 1 2 3 5, 
                     并创建了micro task 放入 micro task queue['第二then 闭包']
                     运行micro task queue首个micro task === 第二then 闭包
                     执行, 控制台 1 2 3 5 6
                  */
})()
</script>

<script>// 创建macro task, 因为stack 为null, 执行
(function test2() {
  setTimeout(function () { // 创建一个macro task 放入 task queue['第一setTimeout闭包' '第二setTimeout闭包']
    console.log(42)
  }, 0);
  new Promise(function executor (resolve) {
    console.log(12); // 执行 控制台1 2 3 5 6 12
    for(var i = 0; i < 10000; i++) {
      i == 9999 && resolve(); // 创建micro task, 当前micro task queue['第一then闭包']
    }
    console.log(22); // 执行 控制台1 2 3 5 6 12 22
  }).then(function() {
    console.log(52);
  });
  console.log(32); /* 执行, 控制台 1 2 3 5 6 12 22 32, 第二个script标签创建的macro task结束
                     运行micro task queue首个micro task === 第一then闭包
                       执行, 控制台 1 2 3 5 6 12 22 32 52, 
                     查看micro task queue首个task为null, 查看task queue
                     task queue为空,向web 获取可执行的任务['第一setTimeout闭包', '第二setTimeout闭包']
                     运行task queue首个 macro task === 第一setTimeout闭包
                     执行, 控制台 1 2 3 5 6 12 22 32 52 4   
                     查看micro task queue首个task为null, 查看task queue
                     运行task queue首个 macro task === 第二setTimeout闭包
                     执行, 控制台 1 2 3 5 6 12 22 32 52 4 42
                  */
})()
</script>

猜想

macro/micro task 进入执行栈时,中间应该会有一个缓存区,
例如

<script> // 创建macro task,进入执行栈
var a = () => {
    console.log('a');
}

var b = () => {
    console.log('b'); 
}

a(); //这时函数A进入stack,执行,打印出a
b(); //这时函数B进入stack,执行,打印出b
</script>

如果是把整个macro task放入执行栈,
按后进程序先执行的机制, 应该会先打印'b',
但打印的是'a',说明函数b是等函数a执行完后再进入执行栈的,
所以在macro task 会把里面的函数拆分为一个执行的队列,放入执行栈里.

示例

图片描述

参考资料

https://github.com/ccforward/cc/issues/48
https://juejin.im/entry/596d78ee6fb9a06bb752475c

Make a friend

作者有一年半的前端开发经验,比较擅长性能优化和vue,喜欢对各种原理的深究,
喜欢篮球和电影
如果有趣味相投可以加入微信群
图片描述


lea_
253 声望19 粉丝

在有限时间,做最好自己.