Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 概览

  • 响应式系统

  • runtime运行时

  • runtime-core

    • nextTick
    • watch和watchEffect
    • provide和inject
    • schedule调度器
      • directive自定义指令
    • compiler编译

    • 《Vue3源码》笔记
    • runtime-core
    东流
    2024-07-17
    目录

    schedule调度器

    # 概述

    schedule调度器是 Vue3 中用于处理异步更新如某些副作用effct、生命周期钩子函数onMounted等的核心模块,它负责将异步更新任务添加到队列中,并适时执行更新操作。其核心实现位于packages\runtime-core\src\scheduler.ts

    # 源码实现

    # job的概念

    schedule是通过队列的方式,所谓的任务job就是要执行的更新操作,除了本身的函数外,一个job可能还会有以下可选属性:

    • id: 任务的唯一标识
    • pre: 是否是预更新任务
    • computed: 是否是计算属性
    • allowRecurse: 是否允许递归更新
    • ownerInstance: 任务所属的组件实例

    # schedule的实现

    schedule维护几个全局变量,如下:

    • queue: 任务队列
    • pendingPostFlushCbs: 待处理的回调函数队列
    • activePostFlushCbs: 正在处理的回调函数队列
    • isFlushPending: 是否还有job等待更新
    • isFlushing: 是否正在执行更新
    • flushIndex: 当前正在执行的job的索引
    • postFlushIndex: 待处理的回调函数的索引
    • currentFlushPromise: 当前正在执行的Promise
    • RECURSION_LIMIT: 递归更新的最大次数 // 内置 100

    schedule主要是通过queueJob和queuePostFlushCb两个函数来添加任务到队列中,而这两个队列有所不同

    • queue: 添加的任务会按照优先级进行排序;这个队列处理的主要是需要异步执行的具体任务,如重新渲染、副作用函数等
    • pendingPostFlushCbs: 添加的任务会按照添加顺序进行排序;这个队列处理的是后置任务,如onUpdated、onUnmounted,后置任务是在主要刷新任务执行完成后才会被执行的任务
    # queue队列的添加和执行

    queueJob函数用于将任务添加到queue队列中,首先会判断queue队列是否为空以及任务是否已经存在于队列中,如果queue队列不为空或者队列不存在该job,则添加job到queue队列中去;添加也有一层判断,如果任务的id为空,则添加任务到queue队列的末尾,否则会通过splice方法将任务插入到队列中,最后调用queueFlush函数来执行刷新任务

    export function queueJob(job: SchedulerJob) {
      if (
        !queue.length ||
        !queue.includes(
          job,
          isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
        )
      ) {
        if (job.id == null) {
          queue.push(job);
        } else {
          queue.splice(findInsertionIndex(job.id), 0, job);
        }
        queueFlush();
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # queuePostFlushCb

    pendingPostFlushCbs用于暂存后置任务,参数cb可能是一个函数或者一个数组

    export function queuePostFlushCb(cb: SchedulerJobs) {
      if (!isArray(cb)) {
        if (
          !activePostFlushCbs ||
          !activePostFlushCbs.includes(
            cb,
            cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex
          )
        ) {
          pendingPostFlushCbs.push(cb);
        }
      } else {
        pendingPostFlushCbs.push(...cb);
      }
      queueFlush();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 队列刷新

    通过上面的代码可以发现,无论是存queue队列还是pendingPostFlushCbs队列,都会调用queueFlush去刷新队列

    function queueFlush() {
      if (!isFlushing && !isFlushPending) {
        isFlushPending = true;
        currentFlushPromise = resolvedPromise.then(flushJobs);
      }
    }
    
    1
    2
    3
    4
    5
    6

    queueFlush是一个内部函数,通过判断isFlushing和isFlushPengding,来决定是否执行flushJobs。前面提到过,isFlushing表示是否有任务正在刷新,isFlushPending表示是否需要刷新队列。 通过Promise.then,将flushJobs放入微任务队列中,等待主线程执行。

    # 刷新任务

    刷新任务flushJobs是一个递归函数,如果queue和pengdingPostFlushCbs队列不为空,则会一直调用执行。

    function flushJobs(seen3) {
      isFlushPending = false;
      isFlushing = true;
      if (true) {
        seen3 = seen3 || /* @__PURE__ */ new Map();
      }
      queue.sort(comparator);
      const check = true ? (job) => checkRecursiveUpdates(seen3, job) : NOOP3;
      try {
        for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
          const job = queue[flushIndex];
          if (job && job.active !== false) {
            if (check(job)) {
              continue;
            }
            callWithErrorHandling(job, null, 14 /* SCHEDULER */);
          }
        }
      } finally {
        flushIndex = 0;
        queue.length = 0;
        flushPostFlushCbs(seen3);
        isFlushing = false;
        currentFlushPromise = null;
        if (queue.length || pendingPostFlushCbs.length) {
          flushJobs(seen3);
        }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    flushJobs会调用comparator方法对queue队列进行排序,排序的依据是job的id,若id相同,则按照job的pre进行排序,然后再调用checkRecursiveUpdates方法来检测是否需要递归更新,最后通过for遍历queue调用callWithErrorHandling方法来执行job,执行完成后会调用flushPostFlushCbs方法来执行后置任务。

    在flushJobs中还会调用flushPostFlushCbs,刷新等待队列的后置任务。其实现如下:

    function flushPostFlushCbs(seen3) {
      if (pendingPostFlushCbs.length) {
        const deduped = [...new Set(pendingPostFlushCbs)].sort(
          (a, b) => getId(a) - getId(b)
        );
        pendingPostFlushCbs.length = 0;
        if (activePostFlushCbs) {
          activePostFlushCbs.push(...deduped);
          return;
        }
        activePostFlushCbs = deduped;
        if (true) {
          seen3 = seen3 || /* @__PURE__ */ new Map();
        }
        for (
          postFlushIndex = 0;
          postFlushIndex < activePostFlushCbs.length;
          postFlushIndex++
        ) {
          if (checkRecursiveUpdates(seen3, activePostFlushCbs[postFlushIndex])) {
            continue;
          }
          activePostFlushCbs[postFlushIndex]();
        }
        activePostFlushCbs = null;
        postFlushIndex = 0;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    flushPostFlushCbs 首先会判断等待队列pendingPostFlushCbs的长度,若其不为空,则通过Set过滤重复job,再通过job的id进行排序,然后就是将排序后的值赋给activePostFlushCbs,循环遍历activePostFlushCbs,直接执行任务

    编辑 (opens new window)
    上次更新: 2024/07/19, 09:43:56
    provide和inject
    directive自定义指令

    ← provide和inject directive自定义指令→

    最近更新
    01
    GeoJSON
    05-08
    02
    Circle
    04-15
    03
    CircleMarker
    04-15
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式