JavaScript中的数据劫持和数据代理使用

 更新时间:2025年02月21日 17:13:09   作者:遇见很ok  
文章主要介绍了数据劫持(Object.defineProperty)和数据代理(Proxy)两种方式在JavaScript中的应用,并通过生活中的例子来详细解释它们的工作原理和使用场景,文章还对比了两种方式的优缺点,并指出了它们在Vue.js中的应用

一、数据劫持(Data Hijacking)

1. 生活中的例子

假设你开了一家餐厅,你雇了一个 “账房先生”(收银员),他的工作是:

  • 任何客人 查询账单 时,他都会记录查询行为,并告诉客人正确的金额。
  • 任何客人 修改账单 时,他都会检查金额是否合理,并记录修改情况。

现实中的“账房先生”就是 Object.defineProperty(),它拦截每个数据的读取和修改

2. 代码示例

我们用 Object.defineProperty() 来模拟这个 “账房先生”:

let bill = {};  // 创建账单对象

Object.defineProperty(bill, 'amount', {
  get() {
    console.log('客人查询了账单金额');
    return 100; // 假设账单金额是100
  },
  set(value) {
    console.log(`客人修改了账单金额为 ${value}`);
  }
});

console.log(bill.amount);  // 触发 get 方法,查询金额
bill.amount = 200;         // 触发 set 方法,修改金额

运行结果:

客人查询了账单金额
100
客人修改了账单金额为 200

问题:

  • 这个方法 只能劫持已sh()pop() 这样的操作不会触发 set
  • 必须对每个属性单独定义 get/set,如
  • 有的属性,如果 bill 里没有 amountdefineProperty 无法监听到新增的 amount 属性。
  • 无法监听数组变化,比如 pu
  • 果对象有很多属性,就要定义很多次,性能较低。

二、数据代理(Data Proxy)

1. 生活中的例子

假设你家有一个 智能管家(类似于小爱同学、Siri),你可以问他:

  • "今天天气怎么样?" 他会去查天气然后告诉你(相当于拦截 读取 操作)。
  • "帮我把灯光调亮一些" 他会控制家里的灯光调亮(相当于拦截 修改 操作)。

智能管家就是 Proxy,它可以代理整个房子里所有的设备,而不需要逐个绑定(不像 Object.defineProperty() 必须单独劫持每个设备的控制)。

2. 代码示例

Proxy 实现智能管家的功能:

let house = { temperature: 22, light: 'off' }; // 房子里的设备

let smartHouse = new Proxy(house, {
  get(target, key) {
    console.log(`查询 ${key} 的状态:${target[key]}`);
    return target[key];
  },
  set(target, key, value) {
    console.log(`修改 ${key} 的状态为 ${value}`);
    target[key] = value;
    return true;
  }
});

console.log(smartHouse.temperature);  // 触发 get,查询温度
smartHouse.light = 'on';              // 触发 set,修改灯光状态

运行结果:

查询 temperature 的状态:22
修改 light 的状态为 on

Proxy 的优势:

  • 可以代理整个对象,不用为每个属性单独定义 get/set
  • 支持监听新增属性,比如 house.newDevice = 'TV' 也能被拦截!
  • 支持数组,例如监听 push()pop() 这些操作。

三、对比总结

对比项数据劫持(Object.defineProperty)数据代理(Proxy)
Vue 版本Vue 2.xVue 3.x
监听对象只能监听已有的属性可以监听整个对象
监听新增/删除属性不能监听(需 Vue.set)可直接监听
监听数组需要重写数组方法原生支持
深层嵌套需要递归遍历访问时自动代理
兼容性ES5 及以上仅支持 ES6 及以上

四、再举一个更贴近开发的例子

使用 Object.defineProperty() 监听对象

let person = { name: 'Alice', age: 25 };

function makeReactive(obj) {
  for (let key in obj) {
    let value = obj[key];
    Object.defineProperty(obj, key, {
      get() {
        console.log(`读取 ${key}: ${value}`);
        return value;
      },
      set(newVal) {
        console.log(`修改 ${key} 为 ${newVal}`);
        value = newVal;
      }
    });
  }
}

makeReactive(person);

console.log(person.name);  // 读取 name
person.age = 30;           // 修改 age

问题:

  • 不能监听 person.newProp = 'test' 这样的新增属性。
  • 不能监听 delete person.age 这样的删除操作。

使用 Proxy 监听整个对象

let person = { name: 'Alice', age: 25 };

let reactivePerson = new Proxy(person, {
  get(target, key) {
    console.log(`读取 ${key}: ${target[key]}`);
    return target[key];
  },
  set(target, key, value) {
    console.log(`修改 ${key} 为 ${value}`);
    target[key] = value;
    return true;
  },
  deleteProperty(target, key) {
    console.log(`删除属性 ${key}`);
    return delete target[key];
  }
});

console.log(reactivePerson.name);  // 读取 name
reactivePerson.age = 30;           // 修改 age
reactivePerson.newProp = 'test';   // 监听新增属性
delete reactivePerson.age;         // 监听删除属性

优势:

  • Proxy 可以监听对象的新增/删除属性,而 Object.defineProperty() 不行!
  • Proxy 可以监听数组的变化,而 Object.defineProperty() 不能监听 push()splice()
  • Proxy 可以代理整个对象,而 Object.defineProperty() 需要遍历所有属性,性能较差。

五、总结

概念方式适用场景优势
数据劫持Object.defineProperty()Vue 2.x兼容性好,支持老浏览器,但不能监听新增/删除属性
数据代理ProxyVue 3.x功能更强,可监听新增/删除,支持数组操作

结论:

  • 如果是 Vue 2,只能用 Object.defineProperty() 来实现数据劫持。
  • 如果是 Vue 3,推荐用 Proxy,因为它更强大,能处理所有数据变化。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

最新评论