一、核心概念与差异概述
特性 | Vue2 | Vue3 |
---|---|---|
实现方式 | Object.defineProperty | Proxy |
数组响应式 | 重写数组方法 | 原生支持 |
新增/删除属性 | 需要 Vue.set/delete | 直接响应 |
性能优化 | 递归初始化所有属性 | 惰性代理 |
嵌套对象处理 | 递归遍历 | 按需代理 |
代码复杂度 | 较高 | 较低 |
二、Vue2 响应式原理详解
1. 核心实现:Object.defineProperty
Vue2 使用 Object.defineProperty 实现响应式:
javascript
function defineReactive(obj, key) {
let value = obj[key]
const dep = new Dep() // 依赖收集器
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) {
dep.depend()
}
return value
},
set(newVal) {
if (newVal === value) return
value = newVal
// 通知更新
dep.notify()
}
})
}
2. 依赖收集系统
javascript
class Dep {
constructor() {
this.subs = new Set()
}
depend() {
if (Dep.target) {
this.subs.add(Dep.target)
}
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
Dep.target = null
// 观察者类
class Watcher {
constructor(getter) {
this.getter = getter
this.value = this.get()
}
get() {
Dep.target = this
const value = this.getter()
Dep.target = null
return value
}
update() {
this.value = this.getter()
console.log('视图更新!')
}
}
3. 数组响应式处理
Vue2 需要特殊处理数组:
javascript
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'
]
methodsToPatch.forEach(method => {
const original = arrayProto[method]
arrayMethods[method] = function(...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// 通知更新
ob.dep.notify()
return result
}
})
4. 完整响应式流程
javascript
class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
// 标记已被观察
Object.defineProperty(value, '__ob__', {
value: this,
enumerable: false,
writable: true,
configurable: true
})
if (Array.isArray(value)) {
// 重写数组方法
value.__proto__ = arrayMethods
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key)
})
}
observeArray(items) {
for (let i = 0; i < items.length; i++) {
observe(items[i])
}
}
}
function observe(value) {
if (typeof value !== 'object' || value === null) return
// 已观察过则直接返回
if (value.hasOwnProperty('__ob__') && value.__ob__ instanceof Observer) {
return value.__ob__
}
return new Observer(value)
}
5. Vue2 响应式使用示例
javascript
const data = {
count: 0,
user: {
name: 'John',
age: 30
},
items: [1, 2, 3]
}
observe(data)
// 创建观察者
new Watcher(() => {
console.log(`Count: ${data.count}`)
})
new Watcher(() => {
console.log(`Items: ${data.items.join(', ')}`)
})
// 测试响应式
data.count++ // 输出: Count: 1
data.items.push(4) // 输出: Items: 1, 2, 3, 4
data.user.name = 'Alice' // 不会触发更新,因为未创建对应Watcher
6. Vue2 响应式局限性
-
新增属性不响应:
javascriptdata.newProp = 'value' // 非响应式
需要使用
Vue.set
:javascriptVue.set(data, 'newProp', 'value')
-
数组索引修改不响应:
javascriptdata.items[0] = 10 // 非响应式
需要使用
splice
:javascriptdata.items.splice(0, 1, 10)
-
性能问题:初始化时递归遍历所有属性
三、Vue3 响应式原理详解
1. 核心实现:Proxy 代理
Vue3 使用 Proxy 实现响应式:
javascript
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key) // 依赖收集
const res = Reflect.get(target, key, receiver)
// 处理嵌套对象
if (typeof res === 'object' && res !== null) {
return reactive(res) // 惰性代理
}
return res
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
trigger(target, key) // 触发更新
}
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
}
})
}
2. 依赖收集系统
javascript
const targetMap = new WeakMap() // 存储所有响应式对象及其依赖
// 当前活动的副作用函数
let activeEffect = null
function track(target, key) {
if (!activeEffect) return
// 获取target对应的depsMap
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取key对应的依赖集合
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
// 添加当前副作用函数
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
// 同时让副作用函数记录依赖集合
activeEffect.deps.push(dep)
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
if (effects) {
// 创建副本避免无限循环
new Set(effects).forEach(effect => effect())
}
}
3. 副作用函数(Effect)
javascript
function effect(fn) {
const effectFn = () => {
try {
// 保存当前活动的副作用函数
const prevActiveEffect = activeEffect
activeEffect = effectFn
// 执行前清除旧依赖
cleanup(effectFn)
return fn()
} finally {
activeEffect = prevActiveEffect
}
}
// 存储该副作用函数依赖的集合
effectFn.deps = []
// 立即执行一次
effectFn()
return effectFn
}
function cleanup(effectFn) {
// 从所有依赖集合中移除该副作用函数
for (const dep of effectFn.deps) {
dep.delete(effectFn)
}
effectFn.deps.length = 0
}
4. 完整响应式流程
javascript
// 创建响应式对象
const state = reactive({
count: 0,
user: {
name: 'John',
age: 30
},
items: [1, 2, 3]
})
// 创建副作用函数
effect(() => {
console.log(`Count: ${state.count}`)
})
effect(() => {
console.log(`Items: ${state.items.join(', ')}`)
})
// 测试响应式
state.count++ // 输出: Count: 1
state.items.push(4) // 输出: Items: 1, 2, 3, 4
state.user.name = 'Alice' // 不会触发更新,因为没有访问user.name
5. Vue3 响应式优势
-
全面响应:
javascriptstate.newProp = 'value' // 响应式 delete state.newProp // 响应式
-
数组索引修改响应:
javascriptstate.items[0] = 10 // 响应式
-
性能优化:
- 惰性代理:只有访问到的属性才会被代理
- 更细粒度的依赖收集
-
支持 Map、Set 等集合类型
6. ref 实现原理
javascript
function ref(value) {
return {
get value() {
track(this, 'value') // 依赖收集
return value
},
set value(newVal) {
if (newVal !== value) {
value = newVal
trigger(this, 'value') // 触发更新
}
}
}
}
// 使用示例
const count = ref(0)
effect(() => {
console.log(`Ref count: ${count.value}`)
})
count.value++ // 输出: Ref count: 1
四、响应式系统对比分析
1. 性能对比
操作 | Vue2 响应式 | Vue3 响应式 |
---|---|---|
初始化 1000 个对象 | 100ms | 50ms |
添加新属性 | 慢(需特殊处理) | 快(原生支持) |
数组操作 | 中等(需重写方法) | 快(原生支持) |
嵌套对象访问 | 递归初始化 | 惰性代理 |
2. 功能对比
功能 | Vue2 | Vue3 |
---|---|---|
属性添加/删除响应 | ❌ | ✅ |
数组索引修改响应 | ❌ | ✅ |
Map/Set 支持 | ❌ | ✅ |
性能优化 | 有限 | 显著 |
代码复杂度 | 高 | 低 |
3. 内存占用对比
Vue3 响应式系统使用 WeakMap 存储依赖关系,当对象不再被引用时,可以自动被垃圾回收,内存管理更高效。
五、实际应用场景
1. 表单绑定(Vue3)
javascript
import { reactive } from 'vue'
export default {
setup() {
const form = reactive({
username: '',
password: '',
remember: false
})
return { form }
}
}
2. 复杂数据操作(Vue3)
javascript
const state = reactive({
users: new Map(),
selectedIds: new Set()
})
// 添加用户
function addUser(id, user) {
state.users.set(id, user)
}
// 选择用户
function toggleUser(id) {
if (state.selectedIds.has(id)) {
state.selectedIds.delete(id)
} else {
state.selectedIds.add(id)
}
}
3. 组件状态管理(Vue3 Composition API)
javascript
import { reactive, computed } from 'vue'
export function useCounter() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
})
function increment() {
state.count++
}
return {
state,
increment
}
}
六、Vue3 响应式进阶特性
1. shallowReactive
javascript
import { shallowReactive } from 'vue'
const state = shallowReactive({
nested: {
count: 0 // 不会自动转换为响应式
}
})
// 需要手动转换
state.nested = reactive(state.nested)
2. readonly
javascript
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 修改原始对象会影响只读副本
original.count++ // 成功
copy.count++ // 失败,控制台警告
3. watchEffect 和 watch
javascript
import { ref, watchEffect, watch } from 'vue'
const count = ref(0)
// 自动追踪依赖
watchEffect(() => {
console.log(`Count: ${count.value}`)
})
// 明确指定侦听源
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`)
})
七、响应式系统最佳实践
-
合理选择响应式 API:
- 对象:使用
reactive
- 基本类型:使用
ref
- 只读数据:使用
readonly
- 对象:使用
-
避免解构响应式对象:
javascript// 错误:解构会丢失响应性 const { x, y } = reactive({ x: 0, y: 0 }) // 正确:使用 toRefs const state = reactive({ x: 0, y: 0 }) const { x, y } = toRefs(state)
-
优化大型列表:
javascript// 使用 shallowRef 避免深度响应 const largeList = shallowRef([])
-
注意循环引用:
javascriptconst obj = reactive({}) obj.self = obj // 可能导致内存泄漏
-
组合式函数封装:
javascript// useFetch.js import { ref } from 'vue' export function useFetch(url) { const data = ref(null) const error = ref(null) fetch(url) .then(res => res.json()) .then(json => data.value = json) .catch(err => error.value = err) return { data, error } }
总结
Vue2 和 Vue3 的响应式系统核心差异在于实现机制:
- Vue2 使用
Object.defineProperty
,需要递归遍历对象属性进行劫持,对数组需要特殊处理 - Vue3 使用
Proxy
,可以代理整个对象,支持更多操作类型,性能更好
Vue3 响应式系统的优势:
- 更全面的响应式覆盖(包括新增/删除属性)
- 更好的性能表现(惰性代理)
- 更简洁的实现代码
- 更好的集合类型支持(Map、Set等)
- 更细粒度的依赖跟踪