响应式原理对比
📖 本节总结
Vue2 和 Vue3 的响应式系统,本质都是数据劫持 + 依赖追踪,但实现方式完全不同。
Vue2 的响应式原理
核心 API:Object.defineProperty
javascript
// Vue2 的响应式实现(简化版)
function defineReactive(obj, key, val) {
// 递归:深层对象也需要响应式
if (typeof val === 'object') {
observe(val)
}
// 核心:使用 defineProperty 拦截 get/set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集:谁在用这个属性
track(obj, key)
return val
},
set(newVal) {
if (val === newVal) return
// 触发更新:通知所有依赖方
trigger(obj, key, newVal)
val = newVal
}
})
}
function observe(obj) {
if (!obj || typeof obj !== 'object') return
// 遍历所有属性,逐个设置为响应式
for (const key in obj) {
defineReactive(obj, key, obj[key])
}
}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
30
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
30
Vue2 响应式初始化流程
javascript
// Vue2 响应式初始化的简化流程
class Vue2Reactive {
constructor(options) {
this._data = options.data
// 将 data 中的所有属性变为响应式
observe(this._data)
}
}
function observe(obj) {
// 创建 Observer 实例
if (!obj.__ob__) {
// 给对象添加 __ob__ 不可枚举属性
Object.defineProperty(obj, '__ob__', {
enumerable: false,
configurable: false,
value: new Observer(obj)
})
}
return obj.__ob__
}
class Observer {
constructor(obj) {
this.walk(obj) // 遍历对象,调用 defineReactive
}
walk(obj) {
for (const key in obj) {
defineReactive(obj, key, obj[key])
}
}
}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
30
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
30
Vue3 的响应式原理
核心 API:Proxy
javascript
// Vue3 的响应式实现(简化版)
function reactive(obj) {
// 使用 Proxy 代理整个对象
return new Proxy(obj, {
get(target, key, receiver) {
// 依赖收集
track(target, key)
// 返回值,如果是深层对象,递归代理
const res = Reflect.get(target, key, receiver)
return typeof res === 'object' ? reactive(res) : res
},
set(target, key, value, receiver) {
const oldValue = target[key]
// 设置新值
const result = Reflect.set(target, key, value, receiver)
// 只有值真正变化才触发
if (oldValue !== value) {
trigger(target, key, value, oldValue)
}
return result
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
trigger(target, key) // 删除也能触发更新
}
return result
},
has(target, key) {
track(target, key)
return Reflect.has(target, key)
}
})
}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
30
31
32
33
34
35
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
30
31
32
33
34
35
Vue3 响应式的优势
javascript
// 1. 新增属性自动响应式
const state = reactive({ name: '张三' })
state.age = 25 // ✅ Vue3 自动响应式
// Vue2 ❌:需要 Vue.set(state, 'age', 25)
// 2. 删除属性自动响应式
delete state.name // ✅ Vue3 自动触发更新
// Vue2 ❌:需要 Vue.delete(state, 'name')
// 3. 数组索引自动响应式
const list = reactive([1, 2, 3])
list[0] = 100 // ✅ Vue3 自动响应式
// Vue2 ❌:需要 Vue.set(list, 0, 100)1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
依赖收集机制对比
Vue2 的依赖收集
javascript
// Vue2 依赖收集器
class Dep {
constructor() {
this.subs = [] // 存放所有的 watcher
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
// 在 getter 中收集依赖
function defineReactive(obj, key, val) {
const dep = new Dep() // 每个属性一个 Dep
Object.defineProperty(obj, key, {
get() {
if (Dep.target) {
// 收集当前的 watcher
dep.addSub(Dep.target)
}
return val
},
set(newVal) {
if (val !== newVal) {
val = newVal
dep.notify() // 通知所有 watcher
}
}
})
}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
30
31
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
30
31
Vue3 的依赖收集
javascript
// Vue3 使用 WeakMap 存储依赖图
// targetMap[target] = depsMap
// depsMap[key] = deps (Set of effects)
const targetMap = new WeakMap()
function track(target, key) {
if (!activeEffect) return
// 获取 target 的依赖图
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取 key 的依赖列表
let deps = depsMap.get(key)
if (!deps) {
deps = new Set() // Set 自动去重
depsMap.set(key, deps)
}
// 添加当前 effect
deps.add(activeEffect)
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
if (effects) {
effects.forEach(effect => effect.run())
}
}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
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
核心数据结构对比
┌─────────────────────────────────────────────────────────┐
│ Vue2 │
├─────────────────────────────────────────────────────────┤
│ 每个响应式对象: │
│ ├── 遍历所有 key,调用 defineReactive │
│ └── 每个 key 创建一个 Dep 实例 │
│ │
│ 依赖关系: │
│ data.name ──────► Dep ──────► Watcher(s) │
│ │ │
│ 多个订阅者 │
│ │
│ 问题: │
│ ❌ 新增属性无法响应 │
│ ❌ 删除属性无法响应 │
│ ❌ 数组索引变化无法响应 │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Vue3 │
├─────────────────────────────────────────────────────────┤
│ 每个响应式对象: │
│ └── 使用 Proxy 代理,自动拦截所有操作 │
│ │
│ 依赖关系: │
│ targetMap (WeakMap) │
│ └── target ──► depsMap (Map) │
│ └── key ──► deps (Set<Effect>) │
│ │
│ 优势: │
│ ✅ 新增属性自动响应 │
│ ✅ 删除属性自动响应 │
│ ✅ 数组索引自动响应 │
│ ✅ 惰性代理,更高效 │
└─────────────────────────────────────────────────────────┘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
30
31
32
33
34
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
30
31
32
33
34
总结
| 对比项 | Vue2 | Vue3 |
|---|---|---|
| 核心 API | Object.defineProperty | Proxy |
| 初始化方式 | 递归遍历所有属性 | 按需懒代理 |
| 新增属性 | 需要 Vue.set | 自动响应 |
| 删除属性 | 需要 Vue.delete | 自动响应 |
| 数组索引 | 需要 Vue.set | 自动响应 |
| 深层嵌套 | 完全遍历 | 惰性递归 |
| 内存占用 | 较大(每个属性一个 Dep) | 较小(按需分配) |