【透彻讲解】Proxy 和 Object.defineProperty 的区别:数据代理 vs 数据劫持

前言

在 Vue2 和 Vue3 的响应式原理中,我们经常会听到"数据劫持"和"数据代理"这两个概念。它们分别对应着 Object.defineProperty 和 Proxy 这两种技术实现。本文将彻底解析它们的区别,通过代码示例、参数说明和使用场景对比,让你完全掌握这两种技术。

一、基本概念对比

特性 Object.defineProperty Proxy
ES版本 ES5 ES6
拦截维度 属性级别 对象级别
数组处理 需要特殊处理 原生支持
新增属性 需要手动监听 自动拦截
性能 较好 更优

二、Object.defineProperty(数据劫持)

1. 基本用法

js 复制代码
const obj = {};
let value = '初始值';

Object.defineProperty(obj, 'property', {
  get: function() {
    console.log('获取值');
    return value;
  },
  set: function(newValue) {
    console.log('设置值', newValue);
    value = newValue;
  }
});

obj.property; // 控制台输出"获取值"
obj.property = '新值'; // 控制台输出"设置值 新值"

2. 参数详解

js 复制代码
Object.defineProperty(obj, prop, descriptor)
  • obj:目标对象

  • prop:要定义或修改的属性名

  • descriptor:属性描述符,包含:

    • configurable:是否可删除,默认为 false
    • enumerable:是否可枚举,默认为 false
    • value:属性值
    • writable:是否可写
    • get:getter 函数
    • set:setter 函数

3. 实现简单响应式

js 复制代码
function observe(data) {
  if (!data || typeof data !== 'object') return;
  
  Object.keys(data).forEach(key => {
    let value = data[key];
    observe(value); // 递归子属性
    
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get() {
        console.log(`获取 ${key}: ${value}`);
        return value;
      },
      set(newVal) {
        console.log(`设置 ${key}: ${newVal}`);
        if (value !== newVal) {
          value = newVal;
          observe(newVal); // 新值是对象时继续监听
        }
      }
    });
  });
}

const data = { name: '张三', info: { age: 20 } };
observe(data);

data.name; // 获取 name: 张三
data.info.age = 21; // 获取 info: [object Object] (注意:无法直接监听嵌套属性变化)

4. 局限性

  1. 无法检测对象属性的添加或删除
js 复制代码
 const obj = { a: 1 };
    observe(obj);
    obj.b = 2; // 无法监听新增属性
  1. 数组方法需要特殊处理
js 复制代码
    const arr = [1, 2, 3];
    observe(arr);
    arr.push(4); // 无法监听数组push操作
  1. 需要递归监听属性造成额外的性能开销

三、Proxy(数据代理)

1. 基本用法

js 复制代码
const target = {};
const handler = {
  get(target, prop, receiver) {
    console.log(`获取属性 ${prop}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`设置属性 ${prop} 为 ${value}`);
    return Reflect.set(target, prop, value, receiver);
  }
};

const proxy = new Proxy(target, handler);
proxy.name = '李四'; // 设置属性 name 为 李四
console.log(proxy.name); // 获取属性 name → 李四

2. 参数详解

js 复制代码
const proxy = new Proxy(target, handler)
  • target:目标对象

  • handler:处理器对象,包含"陷阱"方法:

    • get(target, prop, receiver):拦截属性读取
    • set(target, prop, value, receiver):拦截属性设置
    • has(target, prop):拦截in操作符
    • deleteProperty(target, prop):拦截delete操作
    • 等13种拦截操作

3. 实现完整响应式

js 复制代码
function reactive(data) {
  if (!data || typeof data !== 'object') return data;
  
  const handler = {
    get(target, key, receiver) {
      console.log(`获取 ${key}`);
      const result = Reflect.get(target, key, receiver);
      return reactive(result); // 递归代理嵌套对象
    },
    set(target, key, value, receiver) {
      console.log(`设置 ${key} 为 ${value}`);
      return Reflect.set(target, key, reactive(value), receiver);
    },
    deleteProperty(target, key) {
      console.log(`删除 ${key}`);
      return Reflect.deleteProperty(target, key);
    }
  };
  
  return new Proxy(data, handler);
}

const state = reactive({
  name: '王五',
  hobbies: ['篮球', '游泳']
});

state.hobbies.push('跑步'); 
// 获取 hobbies → 设置 2 为 跑步 → 设置 length 为 3

4. 优势体现

  1. 完美监听数组变化
js 复制代码
    const arr = reactive([1, 2, 3]);
    arr.push(4); // 能够正常拦截
  1. 自动监听新增属性
js 复制代码
    const obj = reactive({});
    obj.newProp = '新属性'; // 能够在set中正常拦截
  1. 支持更多拦截操作
js 复制代码
    const proxy = reactive({});
    delete proxy.name; // 可以拦截delete操作
  1. 无需递归遍历自身进行数据劫持

四、核心区别对比

1. 实现原理不同

  • Object.defineProperty:直接修改原对象属性描述符
  • Proxy:创建原对象的代理对象,不修改原对象

2. 拦截粒度不同

js 复制代码
// Object.defineProperty必须指定具体属性
Object.keys(obj).forEach(key => {
  Object.defineProperty(obj, key, {...});
});

// Proxy一次性拦截整个对象
new Proxy(obj, {
  get() {...},
  set() {...}
});

五、Vue2 和 Vue3 响应式原理对比

Vue2 实现:

js 复制代码
// 简化版Vue2响应式原理
class Observer {
  constructor(value) {
    this.walk(value);
  }
  
  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key]);
    });
  }
}

function defineReactive(obj, key, val) {
  // 递归处理子属性
  observe(val);
  
  Object.defineProperty(obj, key, {
    get() {
      console.log(`获取 ${key}`);
      return val;
    },
    set(newVal) {
      if (val === newVal) return;
      console.log(`设置 ${key}`);
      val = newVal;
      observe(newVal);
    }
  });
}

Vue3 实现:

js 复制代码
// 简化版Vue3响应式原理
function reactive(target) {
  return createReactiveObject(target);
}

function createReactiveObject(target) {
  const handler = {
    get(target, key, receiver) {
      track(target, key); // 依赖收集
      const result = Reflect.get(target, key, receiver);
      return isObject(result) ? reactive(result) : result;
    },
    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;
    }
    // 其他陷阱...
  };
  
  return new Proxy(target, handler);
}

七、总结

Vue3 改用 Proxy 实现响应式,解决了 Vue2 中的诸多限制,降低我们的心智负担

希望这篇博文能帮助你彻底理解 Proxy 和 Object.defineProperty 的区别!如果有任何问题,欢迎在评论区留言讨论。

相关推荐
康小汪6 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
前端·vue.js·人工智能·ai编程·最佳实践
智能运维指南6 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
白山编程大哥8 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Flynt9 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
Csvn9 小时前
bundle优化
前端
北冥有鱼10 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
静默回滚10 小时前
视频压到 25 MB 以内,码率要算、音轨要扣、余量要留
前端
用户372037492601810 小时前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能10 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师10 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端