# Vue2源码解析系列-调度系统（todo）

status: Idea Created time: January 27, 2024 4:55 PM

## Vue调度系统

Vue.js 以其高效、灵活的响应式系统著称，而这背后，离不开调度系统（Scheduler）的精妙设计。它负责协调 Vue 应用的更新过程，确保视图与数据的同步，并维护整体的性能表现。

**调度系统的核心职责：**

* 管理 watchers（监听器），当数据发生变化时，通知相关的 watchers 执行。
* 执行组件更新，包括重新计算属性、渲染模板、更新 DOM 等。
* 控制更新的批处理，将多次更新合并为一次，减少 DOM 操作，提升性能。
* 处理异步操作，保证数据的一致性和更新的顺序。

### 如何执行更新

首先需要知道，vue不是即使更新的，当你更新某个状态时，对应的依赖并不会马上进行更新，而是会先存起来，然后每间隔一段时间再统一进行批量更新，这种方式主要是为了优化性能，特别是更新组件时，vue需要进行diff虚拟DOM和更新DOM，这些操作是非常影响性能的，倘若状态一改变就立马更新，那么就很可能遇到这种情况：

```jsx
{
	for(let i=0;i<100;i++){
		this.num++;
	}
}
```

this.num更新了100次，然而其实只有最后一次才是我们需要的，前面99次都是没用的，然后假如vue是即使更新的，那么vue就会做了99次无用功。

vue实际上会通过一个队列（callbacks）来暂存`watcher`，每当有组件、监听器或计算属性等需要更新时，就会将其对应的更新函数按照`wathcer.id`排序放到这个队列中，之后按照一定规律进行批量更新。下面是vue2将需要更新的`watcher`入队的函数代码：

```jsx
const queue: Array<Watcher> = []

export function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  if (has[id] != null) {
    return
  }

  if (watcher === Dep.target && watcher.noRecurse) {
    return
  }

  has[id] = true
  if (!flushing) {
    queue.push(watcher)
  } else {
    // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  if (!waiting) {
    waiting = true

    if (__DEV__ && !config.async) {
      flushSchedulerQueue()
      return
    }
    nextTick(flushSchedulerQueue)
  }
}
```

从代码中可以看到：

1. 首先通过`watcher.id`进行去重，这是为了避免重复更新
2. 如果当前 watcher 是触发更新的 watcher 本身，并且它禁止递归触发，就直接返回，避免无限循环。
3. 判断当前是否正在更新中
   1. 如果不是
      1. 那么直接将watcher对象添加至队尾
   2. 如果是
      1. 根据`wather.id`将`watcher`排序到队列对应的位置，id越小越靠前

`callback`数组保存了模板语法、计算属性、监听器等等更新方法，在适当的时机执行这些更新方法，来进行更新操作。

```jsx
export function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  if (has[id] != null) {
    return
  }

  if (watcher === Dep.target && watcher.noRecurse) {
    return
  }

  has[id] = true
  if (!flushing) {
    queue.push(watcher)
  } else {
    // if already flushing, splice the watcher based on its id
    // if already past its id, it will be run next immediately.
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  // queue the flush
  if (!waiting) {
    waiting = true

    if (__DEV__ && !config.async) {
      flushSchedulerQueue()
      return
    }
    nextTick(flushSchedulerQueue)
  }
}
```

### 更新的时机


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://1425816423.gitbook.io/my-knowledge-base/yuan-ma-jie-xi/vue-yuan-ma-jie-xi/vue2-yuan-ma-jie-xi-xi-lie-diao-du-xi-tong-todo.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
