
本文将详细介绍如何使用Proxy代理数组,监听数组变动并触发额外任务。
在JavaScript中,我们经常需要监听数组的变化,例如当数组通过push、splice等方法修改时,执行一些额外的操作。传统的set访问器只能捕获对整个数组的赋值操作,而无法感知数组内部的变动。为了解决这个问题,我们可以使用Proxy对象来代理数组,从而拦截对数组的各种操作。
Proxy的基本原理
Proxy对象允许你创建一个对象的代理,并拦截对该对象的基本操作,例如读取属性、写入属性、调用函数等。通过定义一个handler对象,我们可以指定在拦截到这些操作时应该执行的自定义行为。
实现数组变动监听
要监听数组的变动,我们需要拦截Proxy对象的set操作。set trap会在属性被赋值时触发,我们可以通过检查被赋值的属性是否为length来判断数组是否发生了变动。
下面是一个示例代码,演示了如何使用Proxy来监听数组的变动,并在数组长度发生变化时将数组的内容持久化到sessionStorage中:
class Environment {
constructor() {
// 从sessionStorage中初始化数组,如果sessionStorage中没有数据,则初始化为空数组
const crumbList =
JSON.parse(sessionStorage.getItem('crumbs') ?? null) ?? [];
this.crumbs = new Proxy(crumbList, {
set(obj, prop, value) {
// debugger;
// console.log({ obj, prop, value });
// 执行默认的set操作
const result = Reflect.set(...arguments);
// 如果被赋值的属性是length,则将数组的内容持久化到sessionStorage中
if (prop === 'length') {
// 将最新的数组内容保存到sessionStorage中
sessionStorage.setItem('crumbs', JSON.stringify(crumbList));
}
return result;
}
});
// 为 `this.crumbs` 代理添加一个自定义 `valueOf` 实现,该实现返回代理对象最新镜像状态的浅拷贝。
Object.defineProperty(this.crumbs, 'valueOf', {
value: function valueOf () {
return [...crumbList];
},
});
}
}
const env = new Environment();
const metricId = 6;
const concept = 'Back orders';
const crumb = { metricId, concept };
env.crumbs.push(crumb);
env.crumbs.push('foo');
env.crumbs.push('bar');
env.crumbs.length = 5;
env.crumbs.push('baz');
env.crumbs.push('biz');
env.crumbs.length = 9;
console.log('... after initial `push` operations and `crumbs.length` changes ...');
console.log({ "current value": env.crumbs });
env.crumbs.shift();
env.crumbs.pop();
env.crumbs.pop();
console.log('... after `shift` and `pop` operations ...');
console.log({ "current value": env.crumbs });
env.crumbs.splice(1, 3);
console.log('... after a final `splice` operation ...');
console.log({ "current valueOf()": env.crumbs.valueOf() });
console.log({ "currently stored JSON": sessionStorage.getItem('crumbs') });代码解释
- Environment类: 创建了一个Environment类,该类使用 Proxy 包装了一个数组,以便能够拦截数组的修改。
- constructor: 构造函数从 sessionStorage 中检索 crumbs 数据,如果不存在则初始化一个空数组。然后,它创建一个新的 Proxy 实例来包装这个数组。
- Proxy: Proxy 接收两个参数:要代理的目标对象(crumbList 数组)和一个处理程序对象。
- set: 处理程序对象定义了一个 set 方法,该方法在代理对象的属性被设置时调用。该方法拦截对代理数组的属性设置操作。
- Reflect.set(...arguments): 这行代码使用 Reflect.set 执行原始的属性设置操作。Reflect 对象提供了一组用于操作对象的静态方法,Reflect.set 允许你以一种更安全和可控的方式设置属性。
- if (prop === 'length'): 此条件检查设置的属性是否为 length。当使用 push、pop、shift、unshift 或 splice 等修改数组的方法时,数组的 length 属性会发生变化。
- sessionStorage.setItem('crumbs', JSON.stringify(crumbList)): 如果 length 属性发生了变化,则此行代码将更新后的 crumbList 数组存储在 sessionStorage 中。JSON.stringify 用于将数组转换为 JSON 字符串,以便可以将其存储在 sessionStorage 中。
- Object.defineProperty: 为 this.crumbs 代理添加一个自定义 valueOf 实现,该实现返回代理对象最新镜像状态的浅拷贝。
注意事项
- 使用Proxy会带来一定的性能开销,因此在性能敏感的场景下需要谨慎使用。
- Proxy只能拦截对Proxy对象的操作,而不能拦截对原始对象的操作。
- 需要注意兼容性问题,Proxy在一些旧版本的浏览器中可能不支持。
总结
通过使用Proxy对象,我们可以方便地监听数组的变动,并在数组发生变化时执行额外的任务。这种方法可以用于实现数据持久化、状态管理等功能,为我们的应用带来更多的灵活性和可扩展性。










