您好,登录后才能下订单哦!
# 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. 组件渲染时创建渲染Watcher 2. 访问数据触发getter,Dep收集当前Watcher 3. 数据变更触发setter,Dep通知Watcher更新 4. Watcher将自身交给Scheduler调度 5. Scheduler安排异步更新任务
Vue中有三种主要Watcher类型:
类型 | 触发时机 | 用途 |
---|---|---|
渲染Watcher | 组件挂载/更新 | 负责视图渲染 |
计算属性Watcher | 依赖数据变更 | 计算属性求值 |
用户Watcher | watch选项定义 | 自定义监听逻辑 |
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
}
}
依赖收集的完整过程:
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: 返回属性值
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) // 执行回调
}
}
}
Vue的异步更新队列基于浏览器的事件循环机制:
// 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)
}
}
nextTick
的工作流程:
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
})
}
}
queueWatcher
的关键优化:
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)
}
}
}
Vue内部处理的优先级顺序:
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()
}
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()
}
}
}
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
}
}
队列管理的完整实现:
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)
}
}
}
队列刷新过程的完整处理:
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)
}
计算属性的优势: 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)
}
}
// 使用memoize示例
import memoize from 'lodash.memoize'
export default {
data() {
return {
largeList: [...]
}
},
computed: {
filteredList: memoize(function() {
return this.largeList.filter(item => {
// 复杂计算逻辑
})
})
}
}
”`javascript // 使用Object.freeze优化大型列表 export default { data() { return { // 冻结大型列表避免响应式开销 hugeList: Object.freeze(generateHugeList()) } }, methods: { updateItem(index, newItem)
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。