代理模式
概念
为其它对象提供一种 以代理控制对这个对象的访问。不允许直接访问。
UML 类图
代码演示
class RealImg {
fileName: string;
constructor(fileName: string) {
this.fileName = fileName;
this.loadFromDist();
}
display() {
console.log(`display... ${this.fileName}`);
}
private loadFromDist() {
console.log(`loading... ${this.fileName}`);
}
}
class ProxyImg {
realImg: RealImg;
constructor(fileName: string) {
this.realImg = new RealImg(fileName);
}
display() {
this.realImg.display();
}
}
const proxyImg = new ProxyImg('xxx.png');
proxyImg.display();
场景
DOM 事件代理
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
</div>
<script>
const div1 = document.getElementById('div1');
div1.addEventListener('click', function (e) {
const target = e.target;
if (e.nodeName === 'A') {
console.log(target.innerHTML);
}
});
</script>
vite server.proxy
nginx 反向代理
server {
listen 8000;
location / {
proxy_pass http://localhost:8001;
}
location /api/ {
proxy_pass http://localhost:8002;
proxy_set_header Host $host;
}
}
反向代理 vs 正向代理
正向代理是客户端的代理(vite proxy serve);反向代理是服务器的代理(nginx)。
Proxy
vue3 使用 proxy 做 data 响应式
// 明星
const star = {
name: 'yoona',
age: 20,
phone: '1866869898',
price: 0,
};
// 经纪人
const agent = new Proxy(star, {
get(target, key) {
if (key === 'phone') {
return '15989898998'; // 经纪人电话
}
if (key === 'price') {
return 100 * 1000; // 出场费
}
},
set(target, key, val): boolean {
if (key === 'price') {
if (val < 100 * 1000) {
throw new Error('价格低了...');
} else {
console.log('报价成功', val);
return Reflect.set(target, key, val);
}
}
// 其他属性不可以设置
return false;
},
});
// 主办方
console.log(agent.name);
console.log(agent.age);
console.log(agent.phone);
console.log(agent.price);
agent.price = 100000000;
Proxy 使用场景
跟踪属性访问
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...', val);
return Reflect.set(target, key, val);
},
});
proxy.name = 'lie';
console.log(proxy.name);
隐藏属性
const hiddenProps = ['grilfrient']; // 需要隐藏的属性 key
const user = {
name: '张三',
grilfrient: '小红',
};
const proxy = new Proxy(user, {
get(target, key) {
if (hiddenProps.includes(key as string)) return undefined;
return Reflect.get(target, key);
},
has(target, key) {
if (hiddenProps.includes(key as string)) return false;
return Reflect.has(target, key);
},
set(target, key, val) {
if (hiddenProps.includes(key as string)) return false;
return Reflect.set(target, key, val);
},
});
console.log('name', proxy.name); // 张三
console.log('grilfrient', proxy.grilfrient); // undefined
验证属性
// 用TS,会有静态类型检查,用不到这个验证。JS里可以有效果
const user = {
name: '张三',
};
const proxy = new Proxy(user, {
get(target, key) {
return Reflect.get(target, key);
},
set(target, key, val) {
if (key === 'name') {
if (typeof val !== 'string') return false;
}
return Reflect.set(target, key, val);
},
});
proxy.name = 10;
console.log(proxy.name); // 张三
记录实例
const userList = new WeakSet(); // 每次初始化 useruser,都记录到这里
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('user1');
const user2 = new ProxyUser('user2');
console.log('userList', userList);
Proxy 注意事项
- 捕获器不变式 这是“红宝书”里的叫法,捕获器即 get,不变式即不能因为 Proxy 而改变对象本身的描述符特性
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'b', {
value: 22,
writable: false,
configurable: false,
});
const proxy = new Proxy(obj, {
get() {
return 222;
},
});
console.log(proxy.a); // 222
console.log(proxy.b); // 报错, b 属性描述符被修改, proxy 不能修改它的值
- this 函数里的 this 是由执行时确认的,而非定义时。
const user = {
name: '张三',
getName() {
console.log('this...', this);
return this.name;
},
};
const proxy = new Proxy(user, {});
user.getName(); // 执行时 this 是 user
proxy.getName(); // 执行时 this 是 proxy
设计原则验证
- 代理和目标分类,解耦
- 代理可自行拓展逻辑
- 目标也可以自行拓展逻辑