-
1. Proxy
-
1.1. 概述
-
1.2. Proxy 构造函数
-
1.3. 处理器方法(Handler Methods)
-
1.3.1. get(target, propKey, receiver):
-
1.3.2. set(target, propKey, value, receiver):
-
1.3.3. has(target, propKey):
-
1.3.4. deleteProperty(target, propKey):
-
1.3.5. ownKeys(target):
-
1.3.6. getOwnPropertyDescriptor(target, propKey):
-
1.3.7. defineProperty(target, propKey, propDesc):
-
1.3.8. preventExtensions(target):
-
1.3.9. getPrototypeOf(target):
-
1.3.10. isExtensible(target):
-
1.3.11. setPrototypeOf(target, proto):
-
1.3.12. apply(target, object, args):
-
1.3.13. construct(target, args):
-
2. 应用场景
-
2.1. 数据校验
-
2.2. 缓存
-
2.3. 虚拟化DOM
-
2.4. 远程对象代理
1. Proxy

1.1. 概述
ES6(ECMAScript 2015)引入了Proxy
对象,属于一种“元编程”(meta programming),即对编程语言进行编程。
这是一种可以用来定义基本操作(如获取或设置属性)的自定义行为的对象。
Proxy 可以让你在访问一个对象之前对其进行拦截,从而实现对对象操作的控制,比如验证、计算、日志记录等。
这对于开发框架、库或者进行某些高级的JavaScript编程非常有用。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问 进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});上面代码对一个空对象架设了一层拦截,重定义了属性的读取( get )和设置( set )行为。
1.2. Proxy 构造函数
-
目标对象(target):你想要对其操作进行拦截的对象。
-
处理器对象(handler):定义了一系列拦截行为的对象,比如如何处理属性的获取(get)、设置(set)、枚举(enumerate)等。
创建一个Proxy
实例的基本语法如下:
const proxy = new Proxy(target, handler);
-
target
:被代理的目标对象。 -
handler
:一个对象,其属性是被拦截操作的类型(如get
,set
等),属性值是对应的处理函数。
Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。
其中, new Proxy() 表示生成一个 Proxy 实例, target 参数表示所要拦截的目标对象, handler 参数也是一个对象,用来定制拦截行为。
下面是另一个拦截读取属性行为的例子。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35上面代码中,作为构造函数, Proxy 接受两个参数。
-
第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有 Proxy 的介入,操作原来要访问的就是这个对象;
-
第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。
比如,上面代码中,配置对象有一个 get 方法,用来拦截对目标对象属性的访问请求。
get 方法的两个参数分别是目标对象和所要访问的属性。
可以看到,由于拦截函数总是返回 35 ,所以访问任何属性都得到 35 。
注意,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxy 对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果 handler 没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"上面代码中, handler 是一个空对象,没有任何拦截效果,访问 proxy 就等同于访问 target 。
1.3. 处理器方法(Handler Methods)
以下是几个常用的处理器方法:
1.3.1. get(target, propKey, receiver):
拦截对象属性的读取,比如 proxy.foo 和 proxy['foo'] 。
get 方法用于拦截某个属性的读取操作,可以接受三个参数,依次为:
-
目标对象、
-
属性名
-
proxy 实例本身(即 this 关键字指向的那个对象)
其中最后一个参数可选。
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});
let obj = Object.create(proto);
obj.foo // "GET foo"上面代码中,拦截操作定义在 Prototype 对象上面,所以如果读取 obj 对象继承的属性时,拦截会生效。
1.3.2. set(target, propKey, value, receiver):
拦截对象属性的设置,比如 proxy.foo = v 或 proxy['foo'] = v ,返回一个布尔值。
set 方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为:
-
目标对象、
-
属性名、
-
属性值
-
Proxy 实例本身
其中最后一个参数可选。
假定 Person 对象有一个 age 属性,该属性应该是一个不大于200的整数,那么可以使用 Proxy 保证 age 的属性值符合要求。
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not aninteger');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于age以外的属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错上面代码中,由于设置了存值函数 set ,任何不符合要求的 age 属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用 set 方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。
1.3.3. has(target, propKey):
拦截 propKey in proxy 的操作,返回一个布尔值。
has 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是 in 运算符。
下面的例子使用 has 方法隐藏某些属性,不被 in 运算符发现。
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false上面代码中,如果原对象的属性名的第一个字符是下划线, proxy.has 就会返回 false ,从而不会被 in 运算符发现
1.3.4. deleteProperty(target, propKey):
拦截 delete proxy[propKey] 的操作,返回一个布尔值。
deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false ,当前属性就无法被 delete 命令删除。
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property1.3.5. ownKeys(target):
拦截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、Object.keys(proxy) ,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。
1.3.6. getOwnPropertyDescriptor(target, propKey):
拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象。
1.3.7. defineProperty(target, propKey, propDesc):
拦截 Object.defineProperty(proxy, propKey,propDesc) 、Object.defineProperties(proxy,propDescs) ,返回一个布尔值。
1.3.8. preventExtensions(target):
拦截 Object.preventExtensions(proxy) ,返回一个布尔值。
1.3.9. getPrototypeOf(target):
拦截 Object.getPrototypeOf(proxy) ,返回一个对象。
1.3.10. isExtensible(target):
拦截 Object.isExtensible(proxy) ,返回一个布尔值。
1.3.11. setPrototypeOf(target, proto):
拦截 Object.setPrototypeOf(proxy, proto) ,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
1.3.12. apply(target, object, args):
拦截 Proxy 实例作为函数调用的操作,比如 proxy(…args) 、 proxy.call(object,…args) 、 proxy.apply(…) 。
1.3.13. construct(target, args):
拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(…args) 。
更多详细内容,请微信搜索“
前端爱好者
“, ⇲ 戳我 查看 。2. 应用场景
-
数据绑定和响应式编程:例如Vue 3的Reactivity系统就利用
Proxy
来监测数据变化,实现自动更新视图。 -
权限控制和验证:在访问特定属性或方法前进行权限检查。
-
日志记录和调试:记录对象的访问和修改历史。
-
虚拟化对象结构:为现有对象提供一个虚拟的、可能具有不同行为的视图。
Proxy
是一个强大的工具,但需要注意的是,它可能会使代码变得难以理解和维护,因此应当谨慎使用。
ES6的Proxy
提供了强大的元编程能力,能够让我们在访问或修改对象时插入自定义逻辑。以下是一些典型的使用场景及示例:
2.1. 数据校验
场景说明:在复杂应用中,尤其是在表单处理和状态管理时,经常需要对输入数据进行校验。使用Proxy
可以在数据赋值时进行验证。
示例:
const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new Error('Age must be an integer');
}
if (value < 0 || value > 120) {
throw new Error('Age must be between 0 and 120');
}
}
target[prop] = value;
return true; // 表示设置成功
}
};
const person = new Proxy({}, validator);
person.age = 25; // 正常赋值
console.log(person.age); // 输出: 25
try {
person.age = 'young'; // 尝试赋予非法值
} catch (e) {
console.error(e.message); // 输出错误信息
}2.2. 缓存
场景说明:对于一些计算密集型或频繁访问的数据,可以通过
Proxy
实现只计算一次并缓存结果的机制。示例:
const cacheHandler = {
get(target, prop, receiver) {
const value = Reflect.get(...arguments);
if (typeof value === 'function') {
return (...args) => {
if (!cacheHandler.cache.has(prop)) {
cacheHandler.cache.set(prop, value(...args));
}
return cacheHandler.cache.get(prop);
};
}
return value;
},
cache: new Map()
};
function expensiveCalculation(a, b) {
console.log('Calculating...');
return a * a + b * b;
}
const cachedCalculation = new Proxy(expensiveCalculation, cacheHandler);
console.log(cachedCalculation(2, 3)); // 输出: Calculating... 然后输出: 13
console.log(cachedCalculation(2, 3)); // 输出: 13,不再重新计算2.3. 虚拟化DOM
场景说明:虽然这不是
Proxy
最直接的应用,但在某些库或框架中,它被用来虚拟化DOM操作,使得框架能更高效地管理UI状态。示例概念:
想象一个简化版的场景,我们不直接操作DOM,而是通过
Proxy
拦截对DOM元素属性的访问和修改,以便于跟踪变化并优化渲染。// 简化的虚拟DOM概念展示,实际应用中会更复杂
const domProxy = (element) => {
return new Proxy(element, {
set(target, prop, value) {
Reflect.set(target, prop, value);
// 这里可以添加逻辑来决定是否重新渲染或更新DOM
console.log(`Updating ${prop} to ${value}`);
return true;
}
});
};
const div = document.createElement('div');
const proxiedDiv = domProxy(div);
proxiedDiv.textContent = 'Hello, World!'; // 触发更新逻辑2.4. 远程对象代理
场景说明:在分布式系统中,可以使用
Proxy
来模拟远程对象的行为,使得客户端可以像操作本地对象一样操作远程服务。示例概念:
class RemoteObjectProxy {
constructor(url) {
this.url = url;
}
get(target, prop) {
return fetch(`${this.url}/${prop}`).then(res => res.json());
}
// ...其他必要的代理逻辑,如set等
}
const proxy = new RemoteObjectProxy('http://api.example.com/data');
proxy.someProperty.then(data => console.log(data)); // 异步获取数据这些示例展示了
Proxy
在不同场景下的灵活性和强大功能,从数据验证到性能优化,再到复杂的架构设计,Proxy
都是一个值得掌握的现代JavaScript特性。
原文始发于微信公众号(前端爱好者):ES6之Proxy
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/290473.html