Vue2 和 Vue3 的响应式原理对比

news/2025/2/24 8:11:17

Object.defineProperty 与 Proxy 对比

  • 前言
  • 一、Vue2 的响应式原理
  • 二、Vue3 的响应式原理
  • 三、性能优化
  • 总结

前言

响应式系统是 Vue 框架的核心机制之一,通俗易懂的来说 vue2需要手动登记,只有被用到的才会被记录vue3全自动监控

一、Vue2 的响应式原理

  1. 核心实现:Object.defineProperty
    Vue2 的响应式通过 数据劫持 实现,其核心是对对象属性的 getter 和 setter 进行拦截。
html" title=javascript>javascript">// 定义响应式对象
function defineReactive(obj, key, val) {
  const dep = new Dep(); // 依赖收集容器(每个属性对应一个 dep)

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() { 
      dep.depend();   // 收集依赖:将 Watcher 添加到 dep 中 
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      dep.notify(); // 触发更新:通知所有 Watcher 执行回调
    }
  });
}

// 递归遍历对象属性
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return;
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]); // 登记监控 key
  });
}

  1. 依赖收集与触发机制
  • Dep 类:每个属性对应一个 Dep 实例,用于存储所有依赖该属性的 Watcher。
  • Watcher 类:代表一个视图或计算属性的依赖,当数据变化时触发回调。

依赖收集流程

  • 组件渲染时触发 getter。
  • 将当前 Watcher(如渲染函数)添加到 Dep 的订阅列表中。

触发更新流程

  • 属性被修改时触发 setter。
  • 通过 dep.notify() 通知所有订阅的 Watcher 执行更新。
  1. 局限性
  • 无法检测新增/删除对象属性
    需使用 Vue.set() 或 Vue.delete() 强制触发响应。
html" title=javascript>javascript">// 动态属性添加 API
function set(target, key, val) {
  if (Array.isArray(target)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  
  if (key in target) {
    target[key] = val
    return val
  }
  
  const ob = target.__ob__
  if (!ob) {
    target[key] = val
    return val
  }
  
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

  • 数组需要特殊处理
    Vue2 重写了数组的 push、pop 等方法,需通过原型链劫持实现响应式。
html" title=javascript>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]
  def(arrayMethods, method, function mutator(...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
  })
})
  • 性能开销大
    初始化时递归遍历对象所有属性,对深层嵌套对象不友好。
    如果对象有 1000 个属性,需要逐个递归,耗时较长。

二、Vue3 的响应式原理

1.核心实现:Proxy
简化版代码实现

html" title=javascript>javascript">// 响应式入口
function reactive(target) {
  const handler = {
    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) {
        trigger(target, key); // 触发更新
      }
      return result;
    }
  };
  return new Proxy(target, handler);
}

// 依赖收集与触发(简化版)
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); // 存储当前激活的 effect
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  effects && effects.forEach(effect => effect());
}

  1. ref 的实现
html" title=javascript>javascript">class RefImpl {
  constructor(value) {
    this._value = isObject(value) ? reactive(value) : value
    this.dep = new Set()
  }

  get value() {
    trackRefValue(this) // 依赖收集
    return this._value
  }

  set value(newVal) {
    if (hasChanged(newVal, this._value)) {
      this._value = isObject(newVal) ? reactive(newVal) : newVal
      triggerRefValue(this) // 触发更新
    }
  }
}

function trackRefValue(ref) {
  if (activeEffect) {
    trackEffects(ref.dep)
  }
}

function triggerRefValue(ref) {
  triggerEffects(ref.dep)
}

  1. 核心改进
  • 动态属性监听
    支持对象属性的动态增删,无需特殊 API。
  • 原生数组响应式
    可直接通过索引修改数组或修改 length。
html" title=javascript>javascript">	const arr = reactive([1, 2, 3]);
	arr[0] = 10; // 触发更新
	arr.length = 1; // 触发更新
  • 惰性代理
    只有被用到的属性才会被追踪。,减少初始化开销。
  • 代码更简单
    仅在实际使用的属性上触发更新,不需要处理各种特殊情况。

三、性能优化

  1. 大型对象初始化
html" title=javascript>javascript">// 包含 1000 个属性的对象
	const bigData = { /* 1000 个属性 */ }
	
	// Vue2:立即递归转换所有属性(耗时)
	const vm = new Vue({ data: { bigData } })
	
	// Vue3:按需代理(初始化极快)
	const state = reactive(bigData)

  1. 动态属性操作
html" title=javascript>javascript">	// Vue2 必须使用特殊API
	Vue.set(vm.data, 'newProp', value)
	
	// Vue3 直接操作
	state.newProp = value

  1. 数组性能测试
html" title=javascript>javascript">	// 10万条数据数组
	const bigArray = new Array(1000).fill(null).map((_, i) => ({ id: i }))
	
	// Vue2 需要 200ms+ 初始化
	new Vue({ data: { bigArray } })
	
	// Vue3 需要 <10ms 初始化
	const reactiveArray = reactive(bigArray) 

总结

  • vue2手动登记,只有被用到的才会被记录,vue3全自动监控。
  • vue3性能更优:惰性代理减少初始化开销。
  • vue3代码更简洁。

http://www.niftyadmin.cn/n/5864102.html

相关文章

一周学会Flask3 Python Web开发-Jinja2模板基本使用

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们平台开发web系统&#xff0c;必须用到网页&#xff0c;单纯的静态网页无法满足我们的需求。我们可以使用模版引擎技术&am…

智能优化算法:莲花算法(Lotus flower algorithm,LFA)介绍,提供MATLAB代码

一、 莲花算法 1.1 算法原理 莲花算法&#xff08;Lotus flower algorithm&#xff0c;LFA&#xff09;是一种受自然启发的优化算法&#xff0c;其灵感来源于莲花的自清洁特性和授粉过程。莲花的自清洁特性&#xff0c;即所谓的“莲花效应”&#xff0c;是由其叶片表面的微纳…

《GNU/Linux Shell命令全解析》

前言: Shell&#xff08;如Bash、Zsh&#xff09;是一个用户应用程序&#xff0c;通过系统调用接口与内核空间进行交互,运行在所示位置在用户空间中。 GNU/Linux内核架构简易图: 1.基础命令行操作 1.1 Shell功能定义 命令行解释器&#xff1a;Shell解释用户输入的命令&#x…

【环境配置】maven,mysql,node.js,vue的快速配置与上手

【环境配置】maven,mysql,node.js,vue的快速配置与上手 我们在利用springbootvue来进行全栈项目开发时&#xff0c;需要做很多的准备工作&#xff0c;其中maven,mysql,node,js和vue的配置是必不可少的。 本期我们尽可能精简地介绍它们的配置以及快速上手。 1.maven 1.1.下载…

推荐一款AI大模型托管平台-OpenWebUI

推荐一款AI大模型托管平台-OpenWebUI 1. OpenWebUI 1. OpenWebUI什么? 官网地址&#xff1a;https://openwebui.com/ GitHub地址&#xff1a; https://github.com/open-webui/open-webui Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台&#xff0c;旨在完全离…

PnP——根据3D与2d图片估计相机运动

引入 当知道n个3D空间点及其投影位置和2d像素点时&#xff0c;如何估计相机的位姿。PnP(Perspective-n-Point) 是求解3D到2D点对运动的方法。 直接线性变换&#xff08;DLT&#xff09; 问题描述 已知一组3D点 P i ( X i , Y i , Z i , 1 ) ⊤ \mathbf{P}_i (X_i, Y_i, …

【Git 学习笔记_27】DIY 实战篇:利用 DeepSeek 实现 GitHub 的 GPG 秘钥创建与配置

文章目录 1 前言2 准备工作3 具体配置过程3.1. 本地生成 GPG 密钥3.2. 导出 GPG 密钥3.3. 将密钥配置到 Git 中3.4. 测试提交 4 问题排查记录5 小结与复盘 1 前言 昨天在更新我的第二个 Vim 专栏《Mastering Vim (2nd Ed.)》时遇到一个经典的 Git 操作问题&#xff1a;如何在 …

【Python量化金融实战】-第1章:Python量化金融概述:1.1量化金融的定义与发展历程

本小节学习建议&#xff1a;掌握Python编程、统计学&#xff08;时间序列分析&#xff09;、金融学基础&#xff08;资产定价理论&#xff09;三者结合&#xff0c;是进入量化领域的核心路径。 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章目录 1.1 量化金…