Vue中Watcher和Scheduler的实现原理是什么

发布时间:2021-12-03 17:44:50 作者:iii
来源:亿速云 阅读:596
# Vue中Watcher和Scheduler的实现原理是什么

## 目录
- [前言](#前言)
- [1. 响应式系统核心概念](#1-响应式系统核心概念)
  - [1.1 数据驱动视图](#11-数据驱动视图)
  - [1.2 依赖收集与派发更新](#12-依赖收集与派发更新)
- [2. Watcher的实现原理](#2-watcher的实现原理)
  - [2.1 Watcher的分类](#21-watcher的分类)
  - [2.2 Watcher的初始化过程](#22-watcher的初始化过程)
  - [2.3 依赖收集的详细流程](#23-依赖收集的详细流程)
  - [2.4 异步更新的处理机制](#24-异步更新的处理机制)
- [3. Scheduler的任务调度](#3-scheduler的任务调度)
  - [3.1 事件循环与任务队列](#31-事件循环与任务队列)
  - [3.2 nextTick的实现机制](#32-nexttick的实现机制)
  - [3.3 批量更新的优化策略](#33-批量更新的优化策略)
  - [3.4 调度优先级控制](#34-调度优先级控制)
- [4. 源码级实现分析](#4-源码级实现分析)
  - [4.1 Dep类的核心实现](#41-dep类的核心实现)
  - [4.2 Watcher类的关键方法](#42-watcher类的关键方法)
  - [4.3 queueWatcher内部逻辑](#43-queuewatcher内部逻辑)
  - [4.4 flushSchedulerQueue解析](#44-flushschedulerqueue解析)
- [5. 性能优化实践](#5-性能优化实践)
  - [5.1 计算属性VS方法](#51-计算属性vs方法)
  - [5.2 避免重复计算的技巧](#52-避免重复计算的技巧)
  - [5.3 大型列表的优化方案](#53-大型列表的优化方案)
- [6. 与React调度系统的对比](#6-与react调度系统的对比)
  - [6.1 设计哲学差异](#61-设计哲学差异)
  - [6.2 任务调度策略对比](#62-任务调度策略对比)
  - [6.3 性能特征分析](#63-性能特征分析)
- [7. 常见问题与解决方案](#7-常见问题与解决方案)
  - [7.1 数据更新但视图未渲染](#71-数据更新但视图未渲染)
  - [7.2 内存泄漏场景分析](#72-内存泄漏场景分析)
  - [7.3 无限循环更新问题](#73-无限循环更新问题)
- [8. 未来发展趋势](#8-未来发展趋势)
  - [8.1 Vue3中的改进](#81-vue3中的改进)
  - [8.2 响应式系统的演进方向](#82-响应式系统的演进方向)
- [结语](#结语)

## 前言

在现代前端框架中,响应式系统是实现数据驱动视图的核心机制。Vue.js通过精巧的Watcher和Scheduler设计,构建了一套高效的变更检测与更新体系。本文将深入剖析Vue 2.x中这两个关键系统的实现原理,从源码层面揭示其工作机制,并探讨相关的性能优化实践。

## 1. 响应式系统核心概念

### 1.1 数据驱动视图

Vue的响应式系统基于"数据变更自动更新视图"的理念,其核心流程可分为三个阶段:

1. **数据劫持**:通过Object.defineProperty对数据对象进行getter/setter拦截
2. **依赖收集**:在getter中收集当前数据的依赖(Watcher实例)
3. **派发更新**:在setter中通知所有依赖进行更新

```javascript
// 简化的响应式实现
function defineReactive(obj, key) {
  const dep = new Dep()
  let val = obj[key]
  
  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.depend()  // 收集依赖
      }
      return val
    },
    set(newVal) {
      val = newVal
      dep.notify()  // 通知更新
    }
  })
}

1.2 依赖收集与派发更新

依赖收集过程中存在三个关键角色:

典型更新流程: 1. 组件渲染时创建渲染Watcher 2. 访问数据触发getter,Dep收集当前Watcher 3. 数据变更触发setter,Dep通知Watcher更新 4. Watcher将自身交给Scheduler调度 5. Scheduler安排异步更新任务

2. Watcher的实现原理

2.1 Watcher的分类

Vue中有三种主要Watcher类型:

类型 触发时机 用途
渲染Watcher 组件挂载/更新 负责视图渲染
计算属性Watcher 依赖数据变更 计算属性求值
用户Watcher watch选项定义 自定义监听逻辑

2.2 Watcher的初始化过程

Watcher构造函数的核心逻辑:

class Watcher {
  constructor(
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    
    // 解析表达式或获取函数
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    
    this.value = this.lazy 
      ? undefined
      : this.get()  // 立即求值并收集依赖
  }
  
  get() {
    pushTarget(this)  // 设置Dep.target
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)  // 触发依赖收集
    } catch (e) {
      // 错误处理
    } finally {
      popTarget()  // 恢复上一个Watcher
      this.cleanupDeps()  // 清理旧依赖
    }
    return value
  }
}

2.3 依赖收集的详细流程

依赖收集的完整过程:

  1. 组件初始化时创建渲染Watcher
  2. 执行render函数访问响应式数据
  3. 触发数据getter,执行dep.depend()
  4. Dep.target(当前Watcher)被添加到dep.subs中
  5. Watcher也记录自己订阅的dep列表
  6. 数据变更时dep通知所有订阅的watcher更新
sequenceDiagram
  participant Component
  participant Watcher
  participant Dep
  participant Data
  
  Component->>Watcher: 创建渲染Watcher
  Watcher->>Data: 访问属性
  Data->>Dep: 触发getter
  Dep->>Watcher: depend()收集依赖
  Note right of Dep: Dep.target.addDep(this)
  Watcher->>Dep: addSub(this)
  Data-->>Component: 返回属性值

2.4 异步更新的处理机制

Watcher更新时的核心方法:

update() {
  if (this.lazy) {
    this.dirty = true  // 计算属性标记脏值
  } else if (this.sync) {
    this.run()  // 同步直接执行
  } else {
    queueWatcher(this)  // 默认进入异步队列
  }
}

run() {
  if (this.active) {
    const value = this.get()  // 重新求值
    if (value !== this.value || isObject(value)) {
      const oldValue = this.value
      this.value = value
      this.cb.call(this.vm, value, oldValue)  // 执行回调
    }
  }
}

3. Scheduler的任务调度

3.1 事件循环与任务队列

Vue的异步更新队列基于浏览器的事件循环机制:

  1. 微任务队列:默认使用Promise.then
  2. 宏任务降级:setImmediate → MessageChannel → setTimeout
// nextTick的实现核心
const callbacks = []
let pending = false

function flushCallbacks() {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

let timerFunc

if (typeof Promise !== 'undefined') {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
} else if (typeof setImmediate !== 'undefined') {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

3.2 nextTick的实现机制

nextTick的工作流程:

  1. 将回调函数推入callbacks数组
  2. 检查pending状态避免重复执行
  3. 通过timerFunc异步执行flushCallbacks
  4. 清空callbacks并执行所有回调
export function nextTick(cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        // 错误处理
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  
  if (!pending) {
    pending = true
    timerFunc()
  }
  
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

3.3 批量更新的优化策略

queueWatcher的关键优化:

  1. 去重处理:使用watcher.id作为唯一标识
  2. 执行顺序:按watcher创建顺序执行
  3. 状态恢复:执行前标记flushing状态
const queue = []
let has = {}
let waiting = false
let flushing = false
let index = 0

function queueWatcher(watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 正在刷新时按id顺序插入
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}

3.4 调度优先级控制

Vue内部处理的优先级顺序:

  1. 组件更新:父→子的创建顺序
  2. 用户Watcher:早于渲染Watcher执行
  3. 自定义指令:在组件更新后调用
function flushSchedulerQueue() {
  flushing = true
  let watcher, id
  
  // 排序保证正确的更新顺序
  queue.sort((a, b) => a.id - b.id)
  
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    id = watcher.id
    has[id] = null
    watcher.run()
  }
  
  // 重置状态
  resetSchedulerState()
}

4. 源码级实现分析

4.1 Dep类的核心实现

class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor() {
    this.id = uid++
    this.subs = []
  }

  addSub(sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub(sub: Watcher) {
    remove(this.subs, sub)
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

4.2 Watcher类的关键方法

Watcher的核心属性和方法:

class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  
  // 清理旧依赖
  cleanupDeps() {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    
    // 交换引用
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }
  
  // 评估getter并重新收集依赖
  evaluate() {
    this.value = this.get()
    this.dirty = false
  }
}

4.3 queueWatcher内部逻辑

队列管理的完整实现:

function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if (!flushing) {
      queue.push(watcher)
    } else {
      // 如果正在刷新,按id顺序插入到正确位置
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    
    // 排队刷新
    if (!waiting) {
      waiting = true
      
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      nextTick(flushSchedulerQueue)
    }
  }
}

4.4 flushSchedulerQueue解析

队列刷新过程的完整处理:

function flushSchedulerQueue() {
  currentFlushTimestamp = getNow()
  flushing = true
  let watcher, id

  // 排序确保:
  // 1. 组件从父到子更新(因为父总是在子之前创建)
  // 2. 用户watcher在渲染watcher之前运行
  // 3. 如果一个组件在父组件的watcher运行期间被销毁,它的watcher可以被跳过
  queue.sort((a, b) => a.id - b.id)

  // 不缓存长度,因为可能会有新的watcher添加进来
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()  // 调用beforeUpdate钩子
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    
    // 开发环境下检查无限循环
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn('无限更新循环')
        break
      }
    }
  }

  // 重置状态前保留队列副本
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // 调用生命周期钩子
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
}

5. 性能优化实践

5.1 计算属性VS方法

计算属性的优势: 1. 缓存机制:依赖未变化时不重新计算 2. 惰性求值:只有被使用时才会计算 3. 依赖追踪:自动管理依赖关系

// 计算属性实现
function initComputed(vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' 
      ? userDef 
      : userDef.get
      
    // 创建内部watcher
    watchers[key] = new Watcher(
      vm,
      getter || noop,
      noop,
      { lazy: true }  // 标记为计算属性
    )
    
    // 在组件实例上定义计算属性
    defineComputed(vm, key, userDef)
  }
}

5.2 避免重复计算的技巧

  1. 合理使用v-once:静态内容只渲染一次
  2. 优化复杂计算:拆分复杂计算属性
  3. 利用缓存:使用memoize函数库
  4. 避免深层监听:指定watch的deep选项要谨慎
// 使用memoize示例
import memoize from 'lodash.memoize'

export default {
  data() {
    return {
      largeList: [...]
    }
  },
  computed: {
    filteredList: memoize(function() {
      return this.largeList.filter(item => {
        // 复杂计算逻辑
      })
    })
  }
}

5.3 大型列表的优化方案

  1. 虚拟滚动:vue-virtual-scroller
  2. 分页加载:滚动加载更多
  3. 非响应式数据:Object.freeze
  4. 减少不必要的响应式:在初始化时定义所有属性

”`javascript // 使用Object.freeze优化大型列表 export default { data() { return { // 冻结大型列表避免响应式开销 hugeList: Object.freeze(generateHugeList()) } }, methods: { updateItem(index, newItem)

推荐阅读:
  1. Vue实现原理是什么
  2. Vue中scoped的实现原理是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue watcher scheduler

上一篇:Cesium如何批量加载立体线

下一篇:网页里段落的html标签是哪些

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》