vue2和vue3的响应式原理

一、vue2的响应式原理

Vue 2 的响应式原理是其核心特性之一,通过 数据劫持 和 发布-订阅模式 实现数据驱动视图更新。以下是 Vue 2 响应式原理的详细解析,涵盖其核心机制、实现细节以及实际应用中的限制。

一、响应式核心思想

Vue 2 的响应式系统基于以下两个核心概念:

  1. 数据劫持(Data Observe)

    • 使用 Object.defineProperty 劫持对象属性,拦截 get 和 set 操作。
    • 当访问或修改属性时,自动触发依赖收集和派发更新。
  2. 发布-订阅模式(Pub/Sub)

    • 在数据变化时通知所有依赖该数据的订阅者(Watcher),触发视图更新。
二、手写一个简易的响应式 
1. 数据劫持(Object.defineProperty)
  • observe:递归处理对象的每个属性。
  • defineReactive:使用 Object.defineProperty 劫持属性:
    • get:收集依赖(通过 Dep.target)。
    • set:触发更新(调用 dep.notify()

Vue 2 通过递归遍历对象的所有属性,使用 Object.defineProperty 将每个属性转换为响应式属性:

class Observer {
  constructor(data) {
    this.observe(data);
  }

  observe(data) {
    if (!data || typeof data !== 'object') return;

    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
    });
  }

  defineReactive(obj, key, val) {
    const dep = new Dep(); // 每个属性对应一个 Dep

    // 递归处理嵌套对象
    this.observe(val);

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: false,
      get: () => {
        // 依赖收集:如果存在当前订阅者(Watcher),添加到 Dep 在 get 操作中,通过全局的 Dep.target 判断当前是否处于订阅者上下文中,如果是,则将订阅者添加到 Dep.subs 中。
        if (Dep.target) {
          dep.addSub(Dep.target); // 收集订阅者
        }
 Dep.target = null;
        return val;
      },
      set: (newVal) => {
        if (newVal === val) return;
        val = newVal;
        // 派发更新:触发所有订阅者的 update 方法
        dep.notify();
      }
    });
  }
}
 2.Dep 类(依赖管理器)
  • subs:存储所有订阅者(Watcher)。
  • addSub:添加订阅者。
  • notify:通知所有订阅者执行 update
class  Dep{

 constructor() {
    this.subs = []; // 存储订阅者
  }

addSub(watcher){
this.subs.push(watcher)
}
noticefy(){
this.subs.forEach(sub=>sub.update())
   
}

}
3. Watcher 类(订阅者)
  • cb:更新回调函数(模拟视图更新)。
  • update:触发回调函数
  • Watcher 是响应式系统的执行单元,负责监听数据变化并执行回调(如更新视图)。
  • 在 Vue 中,每个组件实例都有一个 Watcher,用于监听数据变化并触发组件重新渲染。
class Watcher {
  constructor(cb) {
    this.cb = cb; // 更新回调
  }

  update() {
    this.cb(); // 执行回调
  }
}
const app = new Vue({
  data: {
    message: 'Hello Vue!',
    user: {
      name: 'Alice',
      age: 25
    }
  }
});

// 模拟视图更新(订阅者)
const watcher = new Watcher(() => {
  console.log('数据变化了,视图需要更新!');
});
/*
/*
订阅者(Watcher)执行时,设置 Dep.target = this。
读取响应式属性时,触发 get,将订阅者添加到 Dep.subs。
完成后清空 Dep.target。
*/

// 订阅 message 属性(手动绑定)
Dep.target = watcher; // 设置当前订阅者
app.$data.message;     // 触发 getter,收集依赖
Dep.target = null;     // 清空当前订阅者

// 修改数据,触发更新
app.$data.message = 'Hello World!'; // 控制台输出:数据变化了,视图需要更新!
1. 依赖收集的核心机制
  • 当一个 Watcher(订阅者)访问响应式数据时,会触发 Object.defineProperty 的 getter
  • 在 getter 中,通过检查 Dep.target 是否存在,判断当前是否有订阅者正在监听该数据。
  • 如果存在订阅者(Dep.target 不为 null),则将该订阅者添加到当前属性对应的 Dep 的 subs(订阅者列表)中。
2. 避免“误收集”依赖
  • Dep.target 是一个临时变量,仅在订阅者执行 get 操作时短暂存在。
  • 在 get 操作完成后,Dep.target 会被清空(设为 null),防止后续无关的依赖被错误地收集。
三、Vue 2 的响应式限制
1. 无法检测数组索引和长度的变化

Vue 2 通过 Object.defineProperty 劫持属性,但数组的索引(如 arr[0])和长度(length)是动态的,无法被劫持。因此,直接修改数组索引不会触发更新。

解决方案:
  • Vue 2 提供了 数组变异方法(如 pushpopsplice)的重写,修改数组时需使用这些方法。
  • 使用 this.$set 修改数组索引或对象属性:
this.$set(this.arr, 0, 100); // ✅ 有效
2. 无法检测对象属性的动态添加/删除

由于 Object.defineProperty 只能劫持已存在的属性,动态添加的新属性不会被劫持:

this.obj.newKey = 'value'; // 无效
 解决方案:
  • 使用 this.$set 或 this.$delete 动态添加/删除属性:
this.$set(this.obj, 'newKey', 'value'); // ✅ 有效
this.$delete(this.obj, 'newKey'); // ✅ 有效

二、vue3的响应式原理

在 Vue 3 中,响应式系统是其核心机制之一,通过 Proxy 和 Reflect 实现数据劫持,并结合 依赖收集 和 发布-订阅模式 实现数据驱动视图更新。相比 Vue 2 的 Object.defineProperty,Vue 3 的响应式系统更强大、灵活,解决了 Vue 2 的诸多限制。

 一、Vue 3 响应式原理的核心思想

1. Proxy 替代 Object.defineProperty
  • Vue 2 使用 Object.defineProperty 劫持对象属性,但存在无法检测数组索引变化和动态属性添加的问题。
  • Vue 3 使用 Proxy 对整个对象进行代理,可以拦截所有操作(如 getsetdeletePropertyhas 等),从而解决 Vue 2 的限制。
2. 依赖收集与派发更新
  • 依赖收集:在访问响应式属性时,收集当前的副作用函数(effect)。
  • 派发更新:在修改响应式属性时,通知所有依赖该属性的副作用函数重新执行。

二、手写一个简易的响应式

1.依赖管理器(Dep

Dep 类

  • effects:存储所有依赖该属性的副作用函数(effect)。
  • track():收集当前激活的副作用函数(activeEffect),将其添加到 effects 中。
  • trigger():触发所有依赖该属性的副作用函数重新执行。
//当访问 state.count 时,track() 会被调用,将当前 effect 收集到 Dep 中;当 state.count 被修改时,trigger() 会执行所有相关的 effect。
class Dep {
  constructor() {
    this.effects = new Set(); // 存储依赖该属性的副作用函数
  }

  track() {
    if (activeEffect) {
      this.effects.add(activeEffect);
    }
  }

  trigger() {
    for (const effect of this.effects) {
      effect();
    }
  }
}

2.全局激活副作用函数(activeEffect

activeEffect 变量

  • 全局变量:保存当前正在执行的副作用函数(effect)。
  • 作用:在访问响应式数据时,用于依赖收集(将 activeEffect 添加到 Dep.effects 中)。
//在 effect(() => console.log(state.count)) 中,activeEffect 会被临时设置为该 effect 函数,从而在访问 state.count 时完成依赖收集。
let activeEffect = null;

3.副作用函数封装(effect

effect 函数

  • runner:包装副作用函数 fn,并在执行前设置 activeEffect,执行后重置。
  • return runner:允许手动调用 runner() 重新执行副作用函数。
/*
第一次调用 effect 时,runner 会被执行,activeEffect 被设置为 runner,从而在访问 state.count 时完成依赖收集。
后续修改 state.count 会触发 Dep.trigger(),再次执行 runner。
*/

function effect(fn) {
  const runner = () => {
    activeEffect = runner;
    fn(); // 执行副作用函数
    activeEffect = null;
  };
  runner();

}

4.响应式对象创建(reactive

 reactive 函数

  • Proxy 拦截 get 和 set
    • get:访问属性时,递归处理嵌套对象,收集依赖(调用 dep.track())。
    • set:修改属性时,如果值变化,触发依赖更新(调用 dep.trigger())。
  • proxyMap:缓存已代理的对象,避免重复代理。
function reactive(target) {
  // 使用 WeakMap 缓存已代理的对象
  const proxyMap = new WeakMap();

  function createReactiveObject(target) {
    // 如果已代理,直接返回
    if (proxyMap.has(target)) {
      return proxyMap.get(target);
    }

    const proxy = new Proxy(target, {
      get(target, key, receiver) {
        const result = Reflect.get(target, key, receiver);

        // 递归处理嵌套对象
        if (typeof result === 'object' && result !== null) {
          return createReactiveObject(result);
        }

        // 依赖收集
        const dep = getDep(target, key);
        dep.track();

        return result;
      },
      set(target, key, value, receiver) {
        const oldValue = target[key];
        const result = Reflect.set(target, key, value, receiver);

        if (result && oldValue !== value) {
          // 派发更新
          const dep = getDep(target, key);
          dep.trigger();
        }

        return result;
      }
    });

    proxyMap.set(target, proxy);
    return proxy;
  }

  return createReactiveObject(target);
}

5.依赖管理(getDep

 getDep 函数

  • targetMap:使用 WeakMap 存储目标对象与 Map 的映射。
  • depsMap:存储属性名与 Dep 的映射。
  • 作用:为每个对象属性分配一个独立的 Dep 实例,用于管理依赖。
/*
对于 state.count,getDep(state, 'count') 会返回对应的 Dep。
如果多次访问 state.count,会复用同一个 Dep。
*/
const targetMap = new WeakMap(); // 存储目标对象与 Map 的映射

function getDep(target, key) {
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Dep();
    depsMap.set(key, dep);
  }
  return dep;
}

const state = reactive({
  count: 0,
  user: {
    name: 'Alice',
    age: 25
  }
});

effect(() => {
  console.log('Count changed:', state.count);
});

effect(() => {
  console.log('User name changed:', state.user.name);
});

state.count = 1; // 输出: Count changed: 1
state.user.name = 'Bob'; // 输出: User name changed: Bob
流程分析:
  1. 创建响应式对象

    • state 是 reactive({ count: 0, user: { ... } }) 创建的响应式对象。
    • user 是嵌套对象,reactive 会递归处理,使其属性变为响应式。
  2. 定义副作用函数

    • 第一个 effect 依赖 state.count
    • 第二个 effect 依赖 state.user.name
  3. 修改数据

    • state.count = 1
      • 触发 set 拦截。
      • 找到 state.count 对应的 Dep,调用 trigger()
      • 执行第一个 effect,输出 Count changed: 1
    • state.user.name = 'Bob'
      • 触发 set 拦截。
      • 找到 state.user.name 对应的 Dep,调用 trigger()
      • 执行第二个 effect,输出 User name changed: Bob
+----------------+       +------------------+       +-----------------+
| 响应式数据     |       | Proxy 拦截操作   |       | 副作用函数      |
| (reactive/ref) |<----->| (get/set/delete) |<----->| (effect/渲染函数)|
+----------------+       +------------------+       +-----------------+
                            |        ^
                            |        |
                            v        |
                      +---------------------+
                      | 依赖收集与触发更新  |
                      | (track/trigger)     |
                      +---------------------+

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值