代理模式
结构型模式
特点
代理模式的核心在于通过中间层控制对目标对象的访问,实现权限控制、缓存优化等功能。
- 针对一个对象
- 设置代理,控制这个对象的访问
- 用户不能直接访问对象,而要通过代理
- 对比装饰器模式,装饰器不可以改变原有对象的功能,而代理则可以
基本使用
代码
ts
class RealImg {
fileName: string;
constructor(fileName: string) {
this.fileName = fileName;
}
display() {
this.loadFromDisk();
console.log(`display... ${this.fileName}`);
}
private loadFromDisk() {
console.log(`loading... ${this.fileName}`);
}
}
class ProxyImg {
private realImg: RealImg;
constructor(fileName: string) {
this.realImg = new RealImg(fileName);
}
// 代理
display() {
// 这里可以做一些其他的操作,比如缓存,权限校验等
this.realImg.display();
}
}
const proxyImg = new ProxyImg('xxx.png');
/*
loading... xxx.png
display... xxx.png
*/
proxyImg.display();使用 UML 图表示

使用场景
代理模式适用于需要控制对象访问权限或优化性能的场景,如远程代理、虚拟代理等。
DOM 事件代理(委托)
- 事件绑定到父容器上,而非目标节点
- 适合目标较多或数量不确定(如无限加载的瀑布流图片列表)
html
<div id="container">
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
<a href="#">4</a>
</div>
<script>
const container = document.getElementById('container');
container.addEventListener('click', (e) => {
if (e.target.tagName === 'A') {
alert(e.target.textContent);
}
});
</script>Webpack devSever proxy
- 开发环境,前端请求服务端 API
- 代理到本地服务器,或者 mock 接口
- 正向代理,客户端代理
js
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}Nginx 反向代理
- 对比正向代理,反向代理是服务端代理
nginx
server {
listen 80;
location / {
proxy_pass http://localhost:3000;
}
location /api {
proxy_pass http://localhost:3001;
proxy_set_header Host $host;
}
}Proxy 应用
Proxy 对象是 JavaScript 中实现代理模式的标准化接口,可以拦截并自定义对象的操作。
模拟明星&经纪人
ts
// 明星
const star = {
name: '张三',
age: 20,
phone: '13111111111',
price: 0.0, // 明星不谈钱
};
// 经纪人
const agent = new Proxy(star, {
get(target, key) {
if (key === 'phone') {
return '13122222222'; // 经纪人的电话,明星电话保密
}
return Reflect.get(target, key);
},
set(target, key, val) {
if (key === 'price') {
if (val < 10 * 10000) {
throw new Error('价格太低');
} else {
console.log('合作成功');
return Reflect.set(target, key, val);
}
}
return false;
},
});
console.log(agent.phone); // 13122222222
// error: 价格太低
agent.price = 10;
agent.price = 100000; // 合作成功使用场景
代理模式适用于需要控制对象访问权限或优化性能的场景,如远程代理、虚拟代理等。
- 跟踪属性
ts
const user = {
name: '张三',
};
const proxy = new Proxy(user, {
get(target, key) {
console.log(`get...`); // 监听
return Reflect.get(target, key);
},
set(target, key, val) {
console.log(`set...`); // 监听
return Reflect.set(target, key, val);
},
});
proxy.name = '李四';
proxy.name;- 隐藏属性
ts
const user = {
name: '张三',
age: 18,
};
const hiddenFields = ['age'];
const proxy = new Proxy(user, {
get(target, key) {
if (hiddenFields.includes(key as string)) {
return undefined;
}
return Reflect.get(target, key);
},
set(target, key, val) {
if (hiddenFields.includes(key as string)) {
return false;
}
return Reflect.set(target, key, val);
},
has(target, key) {
if (hiddenFields.includes(key as string)) {
return false;
}
return Reflect.has(target, key);
},
});
proxy.age = 10; // 报错
console.log(proxy.age); // undefined
console.log(proxy.hasOwnProperty('age')); // false- 验证属性
ts
const user = {
name: '张三',
age: 18,
};
const proxy = new Proxy(user, {
set(target, key, val) {
if (key === 'age' && typeof val !== 'number') {
throw new Error('年龄必须是数字');
}
return Reflect.set(target, key, val);
},
});
proxy.age = '10'; // error: 年龄必须是数字- 记录实例
ts
const userList = new Set();
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
const ProxyUser = new Proxy(User, {
construct(...args) {
const user = Reflect.construct(...args);
userList.add(user); // 记录实例
return user; // 返回实例
},
});
const user1 = new ProxyUser('张三');
const user2 = new ProxyUser('李四');
/*
Set(2) {
User {
name: "张三",
},
User {
name: "李四",
},
}
*/
console.log(userList);可能遇到的坑
- 捕获器不变式
比如:原有的属性描述符不可改变
ts
const obj = {
x: 100,
y: 0,
};
Object.defineProperty(obj, 'y', {
value: 200,
writable: false,
configurable: false,
});
const proxy = new Proxy(obj, {
get() {
return 'abc';
},
});
console.log(proxy.x); // abc
// 原有的属性描述符不可改变
console.log(proxy.y); // TypeError: Proxy handler's 'get' result of a non-configurable and non-writable property should be the same value as the target's property- 关于 this
this 指向的是代理对象,而不是目标对象
ts
const user = {
name: '张三',
getName() {
console.log('this', this);
return this.name;
},
};
const proxy = new Proxy(user, {});
user.getName(); // this => user
proxy.getName(); // this => Proxy