Vue2 与 Vue3 响应式数据原理详解

Vue2 与 Vue3 响应式数据原理详解

一、核心本质

Vue 响应式核心目标:数据变化 → 自动更新视图,底层实现思路统一:

  1. 劫持/监听数据的读取(get):收集依赖(记录哪些视图、函数依赖当前数据)
  2. 劫持/监听数据的修改(set) :触发依赖(通知所有收集的函数更新视图) 二者最大区别:Vue2 使用 Object.defineProperty,Vue3 使用 Proxy + Reflect

二、Vue2 响应式:Object.defineProperty

1. 原理介绍

Object.defineProperty(obj, prop, descriptor) 给对象单个属性劫持 get/set

  • get() :组件渲染、计算属性、watch 读取数据时触发,收集当前正在执行的渲染函数/副作用函数,存入Dep(依赖收集器)
  • set():数据被赋值修改时触发,Dep 遍历内部所有依赖,执行更新函数,刷新视图。

2. 基础简易模拟

js 复制代码
// 依赖收集容器
class Dep {
  constructor() {
    this.subs = []; // 存放依赖(渲染watcher)
  }
  // 收集依赖
  addSub(watcher) {
    this.subs.push(watcher);
  }
  // 派发更新
  notify() {
    this.subs.forEach(watcher => watcher.update());
  }
}

// 观察者:数据劫持
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return;
  Object.keys(obj).forEach(key => {
    let val = obj[key];
    const dep = new Dep();
    Object.defineProperty(obj, key, {
      get() {
        // 有正在执行的watcher则收集依赖
        Dep.target && dep.addSub(Dep.target);
        return val;
      },
      set(newVal) {
        if (newVal === val) return;
        val = newVal;
        observe(newVal); // 新值递归劫持
        dep.notify(); // 通知更新
      }
    });
    observe(val); // 递归深层劫持对象
  })
}

// 渲染Watcher:视图更新函数
class Watcher {
  constructor(fn) {
    Dep.target = this;
    this.updateFn = fn;
    this.update();
    Dep.target = null;
  }
  update() {
    this.updateFn();
  }
}

// 测试
const data = { name: "vue2" };
observe(data);
new Watcher(() => console.log("视图更新:", data.name));
data.name = "更新了";

3. Vue2 致命缺陷

  1. 无法监听对象新增、删除属性 defineProperty 只能劫持初始化存在的属性,新增this.obj.a=1、删除delete this.obj.a无法触发set,因此需要 Vue.set / this.$setthis.$delete
  2. 数组监听缺陷 无法监听数组下标修改、数组长度修改,只能重写7个数组方法(push/pop/shift/unshift/splice/sort/reverse)做拦截。
  3. 递归遍历性能差 初始化时递归遍历对象所有层级,深层大对象初始化开销大。

三、Vue3 响应式:Proxy + Reflect

1. 原理介绍

new Proxy(target, handler) 代理整个对象,而非单个属性:

  • 拦截操作:读取、修改、新增、删除、下标、长度等全部对象操作。
  • 搭配 Reflect:规范对象原生操作,保证this指向、返回值稳定。
  • 依旧沿用 Dep收集依赖、effect副作用函数 的模式。

2. 简易模拟

js 复制代码
class Dep {
  constructor() {
    this.effects = new Set();
  }
  collect() {
    activeEffect && this.effects.add(activeEffect);
  }
  trigger() {
    this.effects.forEach(fn => fn());
  }
}

let activeEffect = null;
// 副作用函数:视图更新逻辑
function effect(fn) {
  activeEffect = fn;
  fn();
  activeEffect = null;
}

// 存储所有对象-key对应的dep
const targetMap = new WeakMap();
function getDep(target, key) {
  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 Dep()));
  return dep;
}

// 数据劫持
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      const dep = getDep(target, key);
      dep.collect();
      return Reflect.get(target, key);
    },
    set(target, key, val) {
      const dep = getDep(target, key);
      const res = Reflect.set(target, key, val);
      dep.trigger();
      return res;
    },
    deleteProperty(target, key) {
      const dep = getDep(target, key);
      const res = Reflect.deleteProperty(target, key);
      dep.trigger();
      return res;
    }
  })
}

// 测试
const state = reactive({ name: "vue3" });
effect(() => console.log("视图更新:", state.name));
state.name = "新版vue";
state.age = 18; // 新增属性可监听
delete state.name; // 删除属性可监听

3. Proxy 对比 defineProperty 的优势

  1. 全覆盖监听 支持属性新增、删除、数组下标、数组长度、in 运算符等全部操作,不再需要 $set
  2. 懒劫持 初始化只代理外层对象,深层属性用到才递归代理,初始化性能大幅优化。
  3. 数组完美监听 原生拦截数组下标、length 修改,不用重写数组方法。
  4. 可拦截更多操作:has、ownKeys、apply 等13种对象行为。

4. 小缺点

Proxy 不兼容 IE 浏览器,Vue3 直接放弃IE支持;Vue2 为兼容老旧浏览器选择 defineProperty。

四、完整响应式闭环流程(Vue2/Vue3通用)

  1. 初始化劫持:对data数据做劫持(defineProperty/Proxy),定义get/set拦截。
  2. 依赖收集 组件挂载执行渲染函数 → 读取响应式数据触发get → 把当前渲染函数存入Dep。
  3. 派发更新 修改数据触发set → Dep取出所有收集的渲染函数 → 执行函数重新渲染DOM。
  4. 异步更新队列 多次连续修改数据不会多次渲染,Vue 用nextTick维护异步队列,合并更新,只刷新一次视图。

五、衍生概念梳理

  1. Watcher:Vue2 中渲染Watcher、计算属性Watcher、侦听器Watcher,存放更新逻辑。
  2. Dep:依赖管理器,一对一绑定数据属性与Watcher。
  3. computed:惰性依赖,只有取值才执行,缓存结果。
  4. watch:主动监听,数据变化立刻执行回调。
  5. nextTick:DOM异步更新,等待DOM刷新完成后执行回调。
相关推荐
用户69371750013841 小时前
深夜炸场!DeepSeek 没发新模型,却重构了整个 Agent 生态
前端·后端·github
岁月留痕1681 小时前
16 实战项目一:实现暗黑模式
前端
岁月留痕1681 小时前
7 Flutter 篇:玩转组件(一)
前端
码农阿豪1 小时前
Ubuntu部署GodoOS:Docker搭建Web办公桌面并实现远程访问
前端·ubuntu·docker
郑州光合科技余经理1 小时前
餐饮预定系统架构拆解:订单链路、权限组织与私有化源码交付
java·开发语言·前端·数据库·人工智能·系统架构·php
满栀5852 小时前
vue3动态路由详细效果
前端·javascript·vue.js·typescript·前端框架
使用小功能大师2 小时前
从零搭建高可用Web应用:全栈架构实战与成本优化完全指南
前端·阿里云·架构·服务搭建
IT_陈寒2 小时前
SpringBoot这个特性差点让我加班到凌晨
前端·人工智能·后端
qq_267612892 小时前
Gitee DevOps的度量驱动持续改进:从构建数据到交付效率的量化管理与优化路径
前端·gitee·自动化