Vue 2 与 Vue 3 响应式原理详细对比

大家好,我是鱼樱!!!

关注公众号【鱼樱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 使用 观察者模式,主要包含三个部分:

  1. Observer:使用 Object.defineProperty 将对象属性转为 getter/setter,劫持数据变化。
  2. Dep:依赖收集器,存储与这个属性相关的所有 Watcher。
  3. 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 的 响应式系统,关键部分:

  1. reactive:创建响应式对象。
  2. effect:副作用函数,追踪依赖并在依赖变化时重新执行。
  3. track:在 getter 中收集依赖。
  4. 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 响应式系统的建议:

  1. 优先使用标准方法:先尝试直接赋值,而不是使用 Vue.set/Vue.delete

  2. 利用 reactive 替代 data 选项:响应式更一致,扩展性更好

  3. 区分值类型和引用类型

    • 基本类型值使用 ref
    • 对象类型使用 reactive
  4. 重构深层对象操作:利用 Vue 3 能够自动跟踪属性添加/删除的能力

  5. 注意响应式丢失的情况

    • 解构 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 的响应式系统差异代表了前端框架发展的重要转变:

  1. Vue 3 响应式系统的主要优势

    • 更全面的对象变更检测
    • 更好的性能,特别是大型或嵌套对象
    • 支持新的数据类型(Map、Set等)
    • API 设计更加灵活且功能强大
  2. Vue 2 响应式系统的局限

    • 无法检测属性添加和删除
    • 无法检测数组索引和长度变化
    • 需要特殊 API 维护响应性
    • 性能受嵌套层级和属性数量影响大
  3. 响应式系统进化的重要意义

    • 催生了组合式 API
    • 推动了更灵活的状态管理模式
    • 简化了复杂应用的开发
    • 提高了框架性能和可用性

Vue 3 的响应式系统是一个根本性的技术进步,它解决了 Vue 2 中的核心限制,为开发者提供了更强大、更灵活的工具来构建现代化的 Web 应用。

相信都看到这里了,肯定是对这个区别了如指掌了。。。面试也不用慌了有东西扯就完了~

相关推荐
巧克力力克巧!10 分钟前
uni-app+vue3学习随笔
vue.js·学习·uni-app
守城小轩12 分钟前
Chrome 扩展开发 API实战:Bookmarks(二)
前端·javascript·chrome
A阳俊yi26 分钟前
SpringMVC中有关请求参数的问题(映射路径,传递不同的参数)
java·前端·javascript
鱼樱前端1 小时前
Vue3 + TypeScript + Better-Scroll 极简上拉下拉组件
前端·javascript·vue.js
影子信息1 小时前
element tree树形结构默认展开全部
前端·javascript·vue.js
Riesenzahn1 小时前
说说你对CSS中@layer的了解
前端·javascript
甜点cc1 小时前
前端每个组件外面套一层el-form,这样好吗?
前端·javascript·vue.js
Riesenzahn1 小时前
说说你对CSS中@container的了解
前端·javascript
Eliauk__1 小时前
Vue 中 Computed 和 Watch 的深入解析与底层实现
前端·javascript·面试
鱼樱前端1 小时前
Vue3 + TypeScript + Better-Scroll 万能上拉下拉组件
前端·javascript·vue.js