vue2和vue3数据响应式原理分析及如何实现

发布时间:2021-12-22 20:22:07 作者:柒染
来源:亿速云 阅读:213
# Vue2和Vue3数据响应式原理分析及如何实现

## 目录
1. [响应式系统概述](#响应式系统概述)
2. [Vue2响应式原理](#vue2响应式原理)
   - [Object.defineProperty实现](#objectdefineproperty实现)
   - [数组处理特殊情况](#数组处理特殊情况)
   - [存在的问题](#存在的问题)
3. [Vue3响应式原理](#vue3响应式原理)
   - [Proxy基础实现](#proxy基础实现)
   - [Reflect的作用](#reflect的作用)
   - [性能优化](#性能优化)
4. [手写实现对比](#手写实现对比)
   - [Vue2风格实现](#vue2风格实现)
   - [Vue3风格实现](#vue3风格实现)
5. [升级变化总结](#升级变化总结)
6. [最佳实践建议](#最佳实践建议)

## 响应式系统概述

响应式编程是Vue的核心机制,其本质是建立**数据与依赖的自动关联**。当数据变化时,所有依赖该数据的相关操作(如DOM更新、计算属性等)都能自动执行。

```javascript
// 理想中的响应式行为
const data = { count: 0 }
watchEffect(() => {
  console.log(`Count is: ${data.count}`)
})
data.count++ // 应自动触发日志输出

Vue2响应式原理

Object.defineProperty实现

Vue2通过Object.defineProperty对数据对象进行递归劫持:

function defineReactive(obj, key) {
  let value = obj[key]
  const dep = new Dep() // 依赖收集器
  
  Object.defineProperty(obj, key, {
    get() {
      if (Dep.target) {
        dep.depend() // 收集依赖
      }
      return value
    },
    set(newVal) {
      if (newVal === value) return
      value = newVal
      dep.notify() // 触发更新
    }
  })
}

// 递归处理整个对象
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return
  new Observer(obj)
}

class Observer {
  constructor(value) {
    if (Array.isArray(value)) {
      // 数组特殊处理
    } else {
      this.walk(value)
    }
  }
  
  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key)
    })
  }
}

数组处理特殊情况

由于Object.defineProperty对数组无效,Vue2采用了拦截器模式:

const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)

['push', 'pop', 'shift', 'unshift'].forEach(method => {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    ob.dep.notify() // 手动触发更新
    return result
  })
})

存在的问题

  1. 初始化递归性能问题:嵌套对象需要深度遍历
  2. 动态新增属性:必须使用Vue.set
  3. 数组限制:通过索引修改元素无法检测
  4. ES6+数据结构:Map/Set等不支持

Vue3响应式原理

Proxy基础实现

Vue3采用ES6的Proxy实现响应式:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key) // 依赖收集
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key) // 触发更新
      return result
    }
  })
}

// 依赖收集与触发
const targetMap = new WeakMap()
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 effects = depsMap.get(key)
  effects && effects.forEach(effect => effect())
}

Reflect的作用

  1. 保证正确的this绑定
  2. 提供更规范的返回值(如Reflect.set返回布尔值)
  3. 与Proxy方法一一对应

性能优化

  1. 惰性响应:只有访问到的属性才会被代理
  2. 缓存机制:同一对象重复reactive返回相同代理
  3. 嵌套处理:仅在访问深层次属性时才会继续代理
function reactive(obj) {
  // 已有代理直接返回
  if (proxyMap.has(obj)) {
    return proxyMap.get(obj)
  }
  
  const proxy = new Proxy(obj, {
    get(target, key, receiver) {
      if (key === '__isReactive') return true
      const res = Reflect.get(target, key, receiver)
      track(target, key)
      return isObject(res) ? reactive(res) : res // 惰性代理
    }
    // ...其他trap
  })
  
  proxyMap.set(obj, proxy)
  return proxy
}

手写实现对比

Vue2风格实现

class Dep {
  constructor() {
    this.subs = new Set()
  }
  depend() {
    if (Dep.target) this.subs.add(Dep.target)
  }
  notify() {
    this.subs.forEach(sub => sub())
  }
}

function defineReactive(obj, key) {
  const dep = new Dep()
  let value = obj[key]
  
  Object.defineProperty(obj, key, {
    get() {
      dep.depend()
      return value
    },
    set(newVal) {
      if (newVal === value) return
      value = newVal
      dep.notify()
    }
  })
}

Vue3风格实现

const effectStack = []

function effect(fn) {
  const e = createReactiveEffect(fn)
  e()
  return e
}

function createReactiveEffect(fn) {
  const effect = function() {
    try {
      effectStack.push(effect)
      return fn()
    } finally {
      effectStack.pop()
    }
  }
  return effect
}

const proxyMap = new WeakMap()
function reactive(target) {
  const existingProxy = proxyMap.get(target)
  if (existingProxy) return existingProxy
  
  const proxy = new Proxy(target, {
    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)
      }
      return result
    }
  })
  
  proxyMap.set(target, proxy)
  return proxy
}

升级变化总结

特性 Vue2 Vue3
核心API Object.defineProperty Proxy
数组处理 方法重写 原生支持
新增属性 需要Vue.set 直接支持
数据类型支持 有限 全面
性能 初始化递归消耗大 按需代理
代码量 约1,000行核心代码 约600行核心代码

最佳实践建议

  1. Vue2项目

    • 复杂对象使用Vue.set
    • 大数组避免索引操作
    • 考虑冻结不需要响应式的数据
  2. Vue3项目

    • 优先使用reactiveref
    • 只读数据使用readonly
    • 利用markRaw跳过代理
  3. 性能优化

    // 避免不必要的响应式
    const staticData = markRaw({
     largeList: [...], // 不会被代理
     config: {...}
    })
    
  4. 组合式开发

    export function useCounter() {
     const count = ref(0)
     const double = computed(() => count.value * 2)
    
    
     function increment() {
       count.value++
     }
    
    
     return { count, double, increment }
    }
    

响应式系统的演进体现了前端技术的快速发展,理解其原理有助于我们编写更高效的Vue应用。 “`

推荐阅读:
  1. vue2如何实现provide inject传递响应式
  2. Vue中数据响应式的原理分析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue

上一篇:Java中的Native方法是什么

下一篇:mysql中出现1053错误怎么办

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》