语法
-
target 要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理
-
handler 一个通常以函数作为属性的对象,用来定制拦截行为
const proxy = new Proxy(target, handle)
举个例子
<script setup lang="ts">
const proxy = {}
const obj = new Proxy(proxy, {
get(target, key, receiver) {
console.log('get', target,key)
// return 10
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', key, value)
// return 20
return Reflect.set(target, key, value, receiver)
}
})
const test1 = () => {
obj.a = 1
}
const test2 = () => {
console.log(obj.a)
}
</script>
<template>
<div>
<button @click="test1">test1</button>
<button @click="test2">test2</button>
</div>
</template>
需要注意的是,代理只会对proxy
对象生效,如上方的origin
就没有任何效果
#Handler 对象常用的方法
方法 | 描述 |
---|---|
handler.has() | in 操作符的捕捉器。 |
handler.get() | 属性读取操作的捕捉器。 |
handler.set() | 属性设置操作的捕捉器。 |
handler.deleteProperty() | delete 操作符的捕捉器。 |
handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。 |
handler.apply() | 函数调用操作的捕捉器。 |
handler.construct() | new 操作符的捕捉器 |
#handler.get
get
我们在上面例子已经体验过了,现在详细介绍一下,用于代理目标对象的属性读取操作
授受三个参数 get(target, propKey, ?receiver)
- target 目标对象
- propkey 属性名
- receiver Proxy 实例本身
举个例子
const person = {
like: "vuejs"
}
const obj = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
}
})
obj.like // vuejs
obj.test // Uncaught ReferenceError: Prop name "test" does not exist.
上面的代码表示在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误
注意:
- 如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同
- 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined
如下面的例子
const obj = {};
Object.defineProperty(obj, "a", {
configurable: false,
enumerable: false,
value: 10,
writable: false
})
const p = new Proxy(obj, {
get: function(target, prop) {
return 20;
}
})
p.a // Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable..
#可撤消的Proxy
proxy
有一个唯一的静态方法,Proxy.revocable(target, handler)
Proxy.revocable()
方法可以用来创建一个可撤销的代理对象
该方法的返回值是一个对象,其结构为: {"proxy": proxy, "revoke": revoke}
- proxy 表示新生成的代理对象本身,和用一般方式 new Proxy(target, handler) 创建的代理对象没什么不同,只是它可以被撤销掉。
- revoke 撤销方法,调用的时候不需要加任何参数,就可以撤销掉和它一起生成的那个代理对象。
该方法常用于完全封闭对目标对象的访问, 如下示例
const target = { name: 'vuejs'}
const {proxy, revoke} = Proxy.revocable(target, handler)
proxy.name // 正常取值输出 vuejs
revoke() // 取值完成对proxy进行封闭,撤消代理
proxy.name // TypeError: Revoked
Proxy的应用场景
Proxy
的应用范围很大,简单举几个例子:
#校验器
const target = {
_id: '1024',
name: 'vuejs'
}
const validators = {
name(val) {
return typeof val === 'string';
},
_id(val) {
return typeof val === 'number' && val > 1024;
}
}
const createValidator = (target, validator) => {
return new Proxy(target, {
_validator: validator,
set(target, propkey, value, proxy){
console.log('set', target, propkey, value, proxy)
let validator = this._validator[propkey](value)
if(validator){
return Reflect.set(target, propkey, value, proxy)
}else {
throw Error(`Cannot set ${propkey} to ${value}. Invalid type.`)
}
}
})
}
const proxy = createValidator(target, validators)
proxy.name = 'vue-js.com' // vue-js.com
proxy.name = 10086 // Uncaught Error: Cannot set name to 10086. Invalid type.
proxy._id = 1025 // 1025
proxy._id = 22 // Uncaught Error: Cannot set _id to 22. Invalid type
#私有属性
在日常编写代码的过程中,我们想定义一些私有属性,通常是在团队中进行约定,大家按照约定在变量名之前添加下划线 _ 或者其它格式来表明这是一个私有属性,但我们不能保证他能真私'私有化',下面使用Proxy轻松实现私有属性拦截
const target = {
_id: '1024',
name: 'vuejs'
}
const proxy = new Proxy(target, {
get(target, propkey, proxy){
if(propkey[0] === '_'){
throw Error(`${propkey} is restricted`)
}
return Reflect.get(target, propkey, proxy)
},
set(target, propkey, value, proxy){
if(propkey[0] === '_'){
throw Error(`${propkey} is restricted`)
}
return Reflect.set(target, propkey, value, proxy)
}
})
proxy.name // vuejs
proxy._id // Uncaught Error: _id is restricted
proxy._id = '1025' // Uncaught Error: _id is restricted
Proxy
使用场景还有很多很多,不再一一列举,如果你需要在某一个动作的生命周期内做一些特定的处理,那么Proxy
都是适合的
#开发中可能遇到的场景
-
增强操作的透明性:可以在不修改目标对象本身的情况下,为其增加额外的功能,比如验证、日志记录、性能监控等。这使得代码更加模块化,易于维护和扩展。
let target = { message: "Hello, world!" };
let handler = {
get(target, prop, receiver) {
console.log(Getting property '${prop}'
);
return Reflect.get(...arguments);
},
set(target, prop, value, receiver) {
console.log(Setting property '${prop}' to '${value}'
);
return Reflect.set(...arguments);
}
};
let proxy = new Proxy(target, handler);proxy.message; // 控制台输出: Getting property 'message'
proxy.message = "Hi there!"; // 控制台输出: Setting property 'message' to 'Hi there!'
2、属性访问控制:允许你控制对对象属性的访问,比如禁止某些属性被修改、只读访问、或者在访问不存在的属性时提供默认值等,这对于构建安全或用户友好的API特别有用
const target = { message: "Read only" };
const handler = {
set(target, prop, value, receiver) {
if (prop === 'message') {
throw new Error("Message is read-only.");
}
return Reflect.set(...arguments);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // 输出: Read only
proxy.message = "New Value"; // 抛出错误: Message is read-only.
3、数据绑定与自动更新 :在前端开发中,尤其是与React、Vue等框架结合时,可以利用Proxy
监听对象属性变化,自动触发UI更新,实现数据双向绑定的效果。
class SimpleReactComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handler = {
get(target, prop) {
return target[prop];
},
set(target, prop, value) {
target[prop] = value;
this.forceUpdate(); // 模拟React组件的更新
return true;
}
};
this.proxyState = new Proxy(this.state, this.handler);
}
increment = () => {
this.proxyState.count++;
};
render() {
return (
<div>
<p>Count: {this.proxyState.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
ReactDOM.render(<SimpleReactComponent />, document.getElementById('root'));
4、资源管理与优化:例如,实现惰性加载(lazy loading),只有当属性第一次被访问时才去加载资源;或者实现缓存机制,减少重复的计算或网络请求。
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
class LazyImage {
constructor(url) {
this.url = url;
this.image = null;
}
async get() {
if (!this.image) {
this.image = await loadImage(this.url);
}
return this.image;
}
}
const proxyImage = new Proxy(new LazyImage("path/to/image.jpg"), {
get(target, prop) {
if (prop === 'src') {
return target.get().then(img => img.src);
}
return Reflect.get(...arguments);
}
});
// 当需要时才真正加载图片
proxyImage.src.then(src => console.log("Loaded image src:", src));
5、模拟对象或环境 :在测试、模拟数据或实现某些高级抽象时,可以使用Proxy
来创建具有特定行为的对象,模拟数据库、文件系统或其他复杂系统的行为。
const db = {
users: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }]
};
const dbProxy = new Proxy(db, {
get(target, prop) {
if (prop === 'getUser') {
return (id) => {
return target.users.find(user => user.id === id);
};
}
return Reflect.get(...arguments);
}
});
console.log(dbProxy.getUser(1)); // 输出: { id: 1, name: "Alice" }
总之,Proxy
提供了一种强大的工具,用于控制对象的访问和操作,它在很多方面都能帮助开发者编写更加灵活、高效和可控的代码。