【Vue进阶】Vue3版本中Proxy源码解读

Vue3版本中Proxy源码解读


Vue3 的响应式系统基于 ES6 的 Proxy 实现,其核心原理是通过代理对象拦截属性的读取和修改等操作,从而自动追踪依赖关系并触发更新。以下是详细说明和代码实现:

一、Proxy 原理解析

  1. Proxy 基本概念
    Proxy 是 JavaScript 的元编程特性,允许创建一个代理对象,拦截并自定义对象的基本操作(如属性访问、赋值等)。

  2. Vue3 响应式流程

读取属性时
修改属性时
创建原始对象
用 Proxy 包装为响应式对象
触发 getter 并收集依赖
触发 setter 并通知更新

二、核心代码实现

  1. 实现简易 reactive 函数
// 存储依赖的容器(WeakMap 防止内存泄漏)
const targetMap = new WeakMap();
let activeEffect = null; // 当前活动的副作用函数

// 响应式入口
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);
      trigger(target, key);
      return true;
    }
  });
}

// 依赖收集
function track(target, key) {
  if (!activeEffect) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }
  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) {
  activeEffect = fn;
  fn(); // 执行首次收集依赖
  activeEffect = null;
}
  1. 测试用例
const obj = reactive({ count: 0 });

// 副作用函数(类似 Vue 的 watchEffect)
effect(() => {
  console.log(`当前计数: ${obj.count}`);
});

obj.count++; // 输出 "当前计数: 1"
obj.count++; // 输出 "当前计数: 2"

三、功能增强实现

  1. 嵌套对象处理
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      track(target, key);
      // 递归代理嵌套对象
      return typeof res === 'object' && res !== null ? reactive(res) : res;
    },
    // set 方法同上...
  });
}

// 测试嵌套对象
const nested = reactive({ foo: { bar: 1 } });
effect(() => {
  console.log(nested.foo.bar); // 自动收集 nested.foo.bar 的依赖
});
  1. 数组处理
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      // 代理数组方法
      if (Array.isArray(target) && ['push', 'pop'].includes(key)) {
        return function(...args) {
          const result = Array.prototype[key].apply(target, args);
          trigger(target, 'length'); // 手动触发更新
          return result;
        }
      }
      // 其他逻辑同上...
    }
  });
}

四、Proxy vs Object.defineProperty

特性Vue3 ProxyVue2 Object.defineProperty
监听范围支持动态新增/删除属性只能监听已存在的属性
数组处理直接监听索引变更和长度变化需要重写数组方法
嵌套对象自动按需代理深层属性必须初始化时递归转换所有属性
性能表现内存占用更低, 处理速度更快大规模数据响应时性能较低
数据类型支持支持 Map/Set/WeakMap 等ES6数据结构只支持普通对象/数组
IE兼容性不支持 IE11 及以下支持 IE9+

五、完整示例代码

<!DOCTYPE html>
<html>
<body>
  <script>
    // ========== 响应式系统核心 ==========
    const targetMap = new WeakMap();
    let activeEffect = null;

    function reactive(target) {
      return new Proxy(target, {
        get(target, key, receiver) {
          const res = Reflect.get(target, key, receiver);
          track(target, key);
          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;
        }
      });
    }

    function track(target, key) { /* 同上 */ }
    function trigger(target, key) { /* 同上 */ }
    function effect(fn) { /* 同上 */ }

    // ========== 测试用例 ==========
    const state = reactive({
      user: { name: 'John', age: 25 },
      scores: [80, 90, 95]
    });

    effect(() => {
      console.log('用户姓名:', state.user.name);
    });

    effect(() => {
      console.log('分数列表:', state.scores.join(','));
    });

    // 触发更新示例
    setTimeout(() => state.user.name = 'Alice', 1000);
    setTimeout(() => state.scores.push(100), 2000);
  </script>
</body>
</html>

六、总结

  1. 核心优势
    Proxy 提供更全面的拦截能力,天然支持动态属性、集合类型和嵌套对象自动代理,大幅简化了 Vue 的响应式实现。

  2. 生产应用
    实际在 Vue3 中会通过 reactive() 创建深度响应式对象,通过 ref() 处理基本类型,并通过 computed()watch() 构建完整的响应式系统。

  3. 性能优化
    Vue3 通过 Proxy 实现对依赖的精准追踪(按属性级追踪),相比 Vue2 的全属性遍历具有显著性能优势。

Proxy对象说明

以下是关于 ES6 Proxy 的详细说明,包含核心概念、使用方法和实际应用示例:

ES6 Proxy 对象详解

一、Proxy 核心概念

  1. 基本定义
    Proxy(代理)是 ES6 引入的元编程特性,用于创建对象的代理,从而拦截和自定义对象的基本操作。

  2. 关键术语
    • Target:被代理的原始对象

• Handler:包含拦截器(traps)的对象

• Trap:拦截特定操作的方法(如 get/set)

二、基础使用方法

  1. 创建 Proxy
const target = { name: 'John' };
const handler = {
  get(target, prop) {
    return target[prop] || 'Not Found';
  }
};

const proxy = new Proxy(target, handler);
console.log(proxy.name); // "John"
console.log(proxy.age);  // "Not Found"
  1. 常用拦截器方法
拦截器触发场景示例用法
get(target, prop)读取属性时属性访问控制、计算属性
set(target, prop, value)设置属性时数据验证、自动更新机制
has(target, prop)in 操作符隐藏属性
deleteProperty(target, prop)delete 操作符防止删除关键属性
apply(target, thisArg, args)函数调用时函数增强/装饰器
construct(target, args)new 操作符单例模式控制

三、进阶应用示例

  1. 数据验证代理
const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value) || value < 0) {
        throw new TypeError('Invalid age value');
      }
    }
    target[prop] = value;
    return true; // 表示设置成功
  }
};

const person = new Proxy({}, validator);
person.age = 25;    // 正常
person.age = '25';  // 抛出 TypeError
  1. 自动格式化代理
const formatter = {
  get(target, prop) {
    const value = target[prop];
    return typeof value === 'number' 
      ? value.toLocaleString('en-US', { style: 'currency', currency: 'USD' })
      : value;
  }
};

const account = new Proxy({ balance: 1500 }, formatter);
console.log(account.balance); // "$1,500.00"
  1. 函数执行代理
function sum(a, b) { return a + b }

const logger = {
  apply(target, thisArg, args) {
    console.log(`Calling ${target.name} with args: ${args}`);
    const result = target(...args);
    console.log(`Result: ${result}`);
    return result;
  }
};

const loggedSum = new Proxy(sum, logger);
loggedSum(2, 3); 
// 输出:
// Calling sum with args: 2,3
// Result: 5

四、实际应用场景

  1. 观察者模式实现
class Observable {
  constructor() {
    this.observers = [];
    return new Proxy(this, {
      set(target, prop, value) {
        const oldValue = target[prop];
        target[prop] = value;
        if (oldValue !== value) {
          target.observers.forEach(fn => fn(prop, value));
        }
        return true;
      }
    });
  }

  subscribe(fn) {
    this.observers.push(fn);
  }
}

const user = new Observable();
user.subscribe((key, val) => console.log(`${key} changed to ${val}`));
user.name = 'Alice'; // 输出 "name changed to Alice"
  1. REST API 客户端
const api = new Proxy({}, {
  get(target, endpoint) {
    return async (params) => {
      const res = await fetch(`https://api.example.com/${endpoint}`, {
        method: 'POST',
        body: JSON.stringify(params)
      });
      return res.json();
    }
  }
});

// 使用方式
api.users({ id: 1 }).then(console.log);
api.products({ category: 'books' }).then(console.log);

五、注意事项

  1. 代理穿透
    使用 Proxy.revocable() 创建可撤销代理:

    const {proxy, revoke} = Proxy.revocable(target, handler);
    revoke(); // 后续操作会抛出 TypeError
    
  2. 性能影响
    • 代理操作比直接对象操作慢约 50%

    • 避免在性能关键路径过度使用

  3. 浏览器兼容性
    兼容性表

六、与 Object.defineProperty 对比

特性ProxyObject.defineProperty
拦截操作范围支持13种操作只能拦截属性读写
数组处理直接拦截数组方法需要重写数组原型方法
属性动态性自动处理新增/删除属性必须预先定义属性
嵌套对象处理支持深层代理需要递归初始化
性能首次初始化快,操作略慢初始化慢,操作快

通过 Proxy 可以实现许多高级编程模式,但需要合理评估使用场景,避免不必要的性能损耗。

关联知识

【设计模式】观察者模式深度讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

问道飞鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值