大家好,我是鱼樱!!!
关注公众号【鱼樱AI实验室】
持续每天分享更多前端和AI辅助前端编码新知识~~喜欢的就一起学反正开源至上,无所谓被诋毁被喷被质疑文章没有价值~
一个城市淘汰的自由职业-农村前端程序员(虽然不靠代码挣钱,写文章就是为爱发电),兼职远程上班目前!!!热心坚持分享~~~
1. 基本实现原理
Vue 2 响应式原理
Vue 2 使用 Object.defineProperty 来实现数据响应式:
js
// 简化版 Vue 2 响应式实现
function defineReactive(obj, key, val) {
const dep = new Dep() // 依赖收集器
// 递归处理嵌套对象
if (typeof val = 'object') {
observe(val)
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 收集依赖
if (Dep.target) {
dep.depend()
}
return val
},
set(newVal) {
if (val = newVal) return
val = newVal
// 如果新值是对象,继续使其响应式
if (typeof newVal = 'object') {
observe(newVal)
}
// 通知依赖更新
dep.notify()
}
})
}
// 观察者函数,处理每个响应式对象
function observe(obj) {
if (!obj || typeof obj ! 'object') return
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
Vue 3 响应式原理
Vue 3 使用 ES6 Proxy 来实现数据响应式:
js
// 简化版 Vue 3 响应式实现
function reactive(target) {
if (!isObject(target)) return target
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
// 依赖收集
track(target, key)
// 如果获取的是对象,则继续使其响应式
return isObject(result) ? reactive(result) : result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
// 触发更新
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
}
}
return new Proxy(target, handler)
}
2. 主要差异对比
特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
---|---|---|
实现方式 | 对象属性拦截 | 整个对象拦截 |
检测新增属性 | 不能,需要使用 Vue.set | 可以,自动检测 |
检测删除属性 | 不能,需要使用 Vue.delete | 可以,自动检测 |
数组索引变化检测 | 不能直接检测 | 可以直接检测 |
数组长度变化检测 | 不能检测 | 可以检测 |
Map/Set 支持 | 不原生支持 | 原生支持 |
嵌套对象处理 | 初始化时递归转换 | 访问时懒转换 |
性能 | 初始化开销大,递归遍历对象 | 初始化轻量,访问时转换 |
3. 详细技术分析
3.1 属性新增和删除
Vue 2:
js
const vm = new Vue({
data: {
user: {
name: 'John'
}
}
})
// 不会触发视图更新
vm.user.age = 25
// 必须使用特定 API 才能触发更新
Vue.set(vm.user, 'age', 25)
// 或
vm.$set(vm.user, 'age', 25)
// 删除属性同理
Vue.delete(vm.user, 'age')
// 或
vm.$delete(vm.user, 'age')
Vue 3:
js
import { reactive } from 'vue'
const user = reactive({
name: 'John'
})
// 自动触发视图更新
user.age = 25
// 删除属性也会自动触发更新
delete user.age
3.2 数组变化检测
Vue 2:
js
const vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
// 不会触发更新
vm.items[1] = 'x'
// 需要使用以下方式:
vm.items.splice(1, 1, 'x')
// 或
Vue.set(vm.items, 1, 'x')
// 修改长度不会触发更新
vm.items.length = 2 // 不响应
Vue 3:
javascript
import { reactive } from 'vue'
const items = reactive(['a', 'b', 'c'])
// 直接修改索引,会触发更新
items[1] = 'x'
// 修改长度也会触发更新
items.length = 2
3.3 集合类型支持
Vue 2: 不原生支持 Map、Set、WeakMap、WeakSet
Vue 3:
js
import { reactive } from 'vue'
const set = reactive(new Set())
set.add(1) // 触发更新
const map = reactive(new Map())
map.set('key', 'value') // 触发更新
3.4 深层嵌套对象处理
Vue 2:
js
// 初始化时递归遍历整个对象树,使每个属性都变成响应式
const vm = new Vue({
data: {
nested: {
level1: {
level2: {
level3: {
value: 'deep'
}
}
}
}
}
})
// 所有层级在初始化时就被转换为响应式
// 如果对象层次深,初始化性能开销大
Vue 3:
js
import { reactive } from 'vue'
const state = reactive({
nested: {
level1: {
level2: {
level3: {
value: 'deep'
}
}
}
}
})
// 只有 state 和 nested 在初始被代理
// 只有当访问 state.nested.level1 时,level1 才被转换为响应式
// 懒处理,提高性能
4. ref 与 reactive 的区别
Vue 3 引入了两种响应式 API:
4.1 reactive
用于对象类型,返回一个响应式代理:
js
import { reactive } from 'vue'
const user = reactive({
name: 'John',
age: 30
})
// 直接访问和修改属性
console.log(user.name)
user.age = 31
4.2 ref
用于基本类型值,将其包装在具有 .value
属性的对象中:
js
import { ref } from 'vue'
const count = ref(0)
// 必须通过 .value 访问和修改
console.log(count.value)
count.value++
当 ref 嵌套在 reactive 对象中时,会自动解包:
js
import { ref, reactive } from 'vue'
const count = ref(0)
const state = reactive({
count,
name: 'Vue'
})
// 自动解包,不需要 .value
console.log(state.count)
state.count++
// 修改原始 ref 也会影响 state.count
count.value++
console.log(state.count) // 已增加
这种区别在 Vue 2 中不存在,因为 Vue 2 只有基于对象的响应式系统。
5. 响应式实现细节
5.1 Vue 2 响应式机制
Vue 2 使用 观察者模式,主要包含三个部分:
- Observer:使用 Object.defineProperty 将对象属性转为 getter/setter,劫持数据变化。
- Dep:依赖收集器,存储与这个属性相关的所有 Watcher。
- Watcher:观察者,执行具体更新操作。
js
// Observer 将对象转换为响应式
class Observer {
constructor(value) {
this.value = value
if (Array.isArray(value)) {
// 重写数组方法
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
observeArray(items) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
// 依赖收集器
class Dep {
constructor() {
this.subs = []
}
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Dep.target = null
5.2 Vue 3 响应式机制
Vue 3 引入了基于 Proxy 的 响应式系统,关键部分:
- reactive:创建响应式对象。
- effect:副作用函数,追踪依赖并在依赖变化时重新执行。
- track:在 getter 中收集依赖。
- trigger:在 setter 中触发更新。
js
// 全局状态
const targetMap = new WeakMap() // 存储目标对象和它们的依赖关系
let activeEffect = null // 当前正在运行的副作用函数
// 创建响应式对象
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver)
// 依赖收集
track(target, key)
// 如果是对象类型,返回响应式版本
return isObject(result) ? reactive(result) : result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(oldValue, value)) {
// 触发更新
trigger(target, key)
}
return result
}
})
}
// 依赖收集
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
// 创建副作用
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn
fn()
activeEffect = null
}
effectFn()
return effectFn
}
6. 关键优势对比
6.1 性能优势
Vue 2:
- 初始化开销大:需要递归遍历整个对象树
- 大型对象性能问题:复杂对象初始化时间长
- 无法追踪属性添加/删除:需要使用特殊 API
Vue 3:
- 懒式初始化:只代理顶层属性,嵌套属性在访问时才代理
- 更精确的变更检测:只在实际访问的属性上添加依赖
- 内存占用更少:不需要为每个属性创建 getter/setter
- 原生支持数组索引和长度变化
- 原生支持集合类型(Map、Set)
6.2 API 设计优势
Vue 3:
- 分离的响应式 API:可以在任何地方创建响应式数据,不限于组件选项
- 精细的依赖追踪:可以明确知道哪个属性依赖哪些副作用函数
- 组合式 API 更好的配合:与 setup、computed、watch 等函数完美配合
- 更好的类型推导:TypeScript 支持更完善
7. 实际应用中的差异
7.1 处理大型对象
Vue 2:
js
export default {
data() {
return {
// 每个属性都会在初始化时递归设置 getter/setter
largeObject: generateLargeObject()
}
}
}
// 性能:初始化缓慢,特别是大对象
Vue 3:
js
import { reactive } from 'vue'
export default {
setup() {
// 只在实际访问属性时设置代理
const largeObject = reactive(generateLargeObject())
return { largeObject }
}
}
// 性能:初始化快速,访问时逐步处理
7.2 动态属性处理
Vue 2:
js
export default {
data() {
return { user: {} }
},
methods: {
addProperty(key, value) {
// 必须使用特殊 API
this.$set(this.user, key, value)
},
removeProperty(key) {
this.$delete(this.user, key)
}
}
}
Vue 3:
js
import { reactive } from 'vue'
export default {
setup() {
const user = reactive({})
function addProperty(key, value) {
// 直接赋值即可
user[key] = value
}
function removeProperty(key) {
// 直接删除即可
delete user[key]
}
return { user, addProperty, removeProperty }
}
}
7.3 嵌套数据结构的处理
Vue 2:
js
export default {
data() {
return {
nested: {
users: [
{ name: 'John', settings: { theme: 'dark' } }
]
}
}
},
methods: {
updateTheme() {
// 直接修改深层属性可以工作
this.nested.users[0].settings.theme = 'light'
// 但是更换整个对象则需要特殊处理
// 假设有新设置对象
const newSettings = { theme: 'light', fontSize: 14 }
// 必须使用特殊 API
this.$set(this.nested.users[0], 'settings', newSettings)
}
}
}
Vue 3:
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
nested: {
users: [
{ name: 'John', settings: { theme: 'dark' } }
]
}
})
function updateTheme() {
// 直接修改深层属性
state.nested.users[0].settings.theme = 'light'
// 替换整个对象也可以直接工作
state.nested.users[0].settings = { theme: 'light', fontSize: 14 }
}
return { state, updateTheme }
}
}
8. 引入的新概念
8.1 shallowReactive 与 shallowRef
Vue 3 引入了浅层响应式,只代理对象的第一层属性:
js
import { shallowReactive } from 'vue'
// 只有顶层属性是响应式的
const state = shallowReactive({
user: {
name: 'John',
settings: { theme: 'dark' }
}
})
state.user = { name: 'Jane' } // 触发更新
state.user.name = 'Mike' // 不触发更新!
8.2 自定义 ref (customRef)
Vue 3 允许创建自定义的 ref 实现,完全控制依赖追踪和更新触发:
js
import { customRef } from 'vue'
// 创建一个带防抖功能的 ref
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track() // 追踪依赖
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 触发更新
}, delay)
}
}
})
}
// 使用这个自定义 ref
const text = useDebouncedRef('hello')
8.3 toRaw 与 markRaw
Vue 3 提供了在响应式系统和原始数据之间转换的方法:
js
import { reactive, toRaw, markRaw } from 'vue'
const original = { count: 0 }
const state = reactive(original)
// 从代理对象获取原始对象
const raw = toRaw(state)
raw === original // true
// 标记对象永远不会转换为代理
const obj = markRaw({ count: 0 })
const state2 = reactive({
nested: obj
})
state2.nested.count++ // 更改会生效
// 但不会触发更新,因为 obj 被标记为永远不需要转换为响应式
9. 实际性能比较
9.1 初始化性能
大型对象初始化:
- Vue 2:随着对象属性数量增加,初始化时间线性增长
- Vue 3:初始化时间几乎不受属性数量影响,保持恒定
深层嵌套对象:
- Vue 2:随着嵌套层级增加,初始化时间线性增长
- Vue 3:初始化时间几乎不受嵌套层级影响
9.2 更新性能
顶层属性更新:
- Vue 2 和 Vue 3 类似
深层嵌套属性更新:
- Vue 2:对深层属性单独处理,更新效率较高
- Vue 3:由于懒初始化,首次读取嵌套属性时略有开销,但后续更新性能与 Vue 2 相当
数组操作:
- Vue 3 在数组索引、长度修改方面明显优于 Vue 2
10. 实际案例分析
10.1 表单处理
Vue 2:
js
export default {
data() {
return {
form: {
name: '',
email: '',
address: {
city: '',
street: ''
}
}
}
},
methods: {
resetForm() {
// 逐个重置属性
this.form.name = ''
this.form.email = ''
// 对于嵌套对象,必须维护原始结构或使用 $set
this.form.address.city = ''
this.form.address.street = ''
}
}
}
Vue 3:
js
import { reactive } from 'vue'
export default {
setup() {
const form = reactive({
name: '',
email: '',
address: {
city: '',
street: ''
}
})
function resetForm() {
// 可以直接替换整个对象
Object.assign(form, {
name: '',
email: '',
address: {
city: '',
street: ''
}
})
// 或者使用扩展运算符
// Object.assign(form, {
// ...initialFormState
// })
}
return { form, resetForm }
}
}
10.2 动态表格
Vue 2:
js
export default {
data() {
return {
users: [
{ id: 1, name: 'John' }
]
}
},
methods: {
addColumn(key, value) {
// 向每个用户添加新列
this.users.forEach(user => {
// 必须使用 $set
this.$set(user, key, value)
})
},
sortUsers() {
// 数组方法是经过包装的,可以直接支持
this.users.sort((a, b) => a.name.localeCompare(b.name))
}
}
}
Vue 3:
js
import { reactive } from 'vue'
export default {
setup() {
const users = reactive([
{ id: 1, name: 'John' }
])
function addColumn(key, value) {
// 直接添加新属性
users.forEach(user => {
user[key] = value
})
}
function sortUsers() {
// 数组方法直接支持
users.sort((a, b) => a.name.localeCompare(b.name))
}
return { users, addColumn, sortUsers }
}
}
11. 迁移策略
从 Vue 2 迁移到 Vue 3 响应式系统的建议:
-
优先使用标准方法:先尝试直接赋值,而不是使用 Vue.set/Vue.delete
-
利用 reactive 替代 data 选项:响应式更一致,扩展性更好
-
区分值类型和引用类型:
- 基本类型值使用 ref
- 对象类型使用 reactive
-
重构深层对象操作:利用 Vue 3 能够自动跟踪属性添加/删除的能力
-
注意响应式丢失的情况:
- 解构 reactive 对象会失去响应性
- 使用 toRefs 保持响应性
js
import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Vue' })
// 错误方式:会丢失响应性
const { count, name } = state
// 正确方式:使用 toRefs 保持响应性
const { count, name } = toRefs(state)
// 现在 count.value 和 name.value 是响应式的
12. 总结
Vue 2 和 Vue 3 的响应式系统差异代表了前端框架发展的重要转变:
-
Vue 3 响应式系统的主要优势:
- 更全面的对象变更检测
- 更好的性能,特别是大型或嵌套对象
- 支持新的数据类型(Map、Set等)
- API 设计更加灵活且功能强大
-
Vue 2 响应式系统的局限:
- 无法检测属性添加和删除
- 无法检测数组索引和长度变化
- 需要特殊 API 维护响应性
- 性能受嵌套层级和属性数量影响大
-
响应式系统进化的重要意义:
- 催生了组合式 API
- 推动了更灵活的状态管理模式
- 简化了复杂应用的开发
- 提高了框架性能和可用性
Vue 3 的响应式系统是一个根本性的技术进步,它解决了 Vue 2 中的核心限制,为开发者提供了更强大、更灵活的工具来构建现代化的 Web 应用。
相信都看到这里了,肯定是对这个区别了如指掌了。。。面试也不用慌了有东西扯就完了~