代理模式(Proxy Pattern)是一种结构型设计模式,通过为另一个对象(主体)提供一个代理或占位符来控制对其的访问。代理对象可以在调用主体前后添加额外逻辑,如访问控制、数据缓存或日志记录,而客户端无需直接与主体交互,保持透明性。
设计模式原理
代理模式的核心是通过代理对象控制对主体对象的访问,代理与主体实现相同的接口(或直接拦截操作),客户端通过代理间接访问主体。代理可以在调用前后添加逻辑,如数据验证、通知或缓存。在Vue.js中,ES6 Proxy拦截对象的 getter 和 setter,跟踪依赖并触发更新。
结构
- 主题接口(Subject):定义代理和主体的公共接口(在ES6 Proxy中,主体是普通对象,接口隐式)。
- 真实主题(RealSubject):实际数据对象(如Vue.js中的data对象)。
- 代理(Proxy):拦截对真实主题的访问,添加响应式逻辑。
- 客户端(Client):通过代理访问数据,如Vue组件。
优点
- 访问控制:代理可以拦截和验证操作,增强数据安全性。
- 功能增强:支持添加响应式、日志或缓存逻辑。
- 透明性:客户端无需感知代理,直接操作数据。
- 动态性:ES6 Proxy提供灵活的拦截机制,适合动态场景。
缺点
- 复杂性增加:代理逻辑可能增加代码复杂度。
- 性能开销:频繁拦截可能带来轻微性能影响。
- 浏览器兼容性:ES6 Proxy不支持旧浏览器(如IE)。
- 调试难度:代理拦截可能使错误追踪更复杂。
适用场景
- 响应式系统:如Vue.js的数据绑定、状态管理。
- 访问控制:限制对敏感数据的操作。
- 日志/监控:记录数据访问和修改日志。
- 延迟加载:按需加载数据或资源。
TypeScript 实现示例
我们实现一个简化的Vue.js响应式系统,使用ES6 Proxy模拟Vue 3的响应式原理。代理拦截数据对象的 getter 和 setter,跟踪依赖并在数据变化时通知视图更新。
项目结构
go
vue-reactive-proxy/
├── src/
│ ├── reactive.ts // 响应式代理实现
│ ├── component.ts // 客户端(Vue组件)
│ ├── main.ts // 执行示例
├── tsconfig.json
├── package.json
1. 安装依赖
bash
npm init -y
npm install typescript @types/node
npx tsc --init
配置 tsconfig.json
:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"moduleResolution": "node"
}
}
2. 实现响应式代理 (reactive.ts)
typescript
// 依赖类,用于跟踪和通知
class Dep {
private subscribers: Set<() => void> = new Set();
depend(fn: () => void): void {
this.subscribers.add(fn);
console.log('依赖已注册');
}
notify(): void {
this.subscribers.forEach(fn => {
fn();
console.log('通知依赖更新');
});
}
}
// 响应式代理
export function reactive<T extends object>(target: T): T {
const deps: Map<string | symbol, Dep> = new Map();
return new Proxy(target, {
get(target: T, key: string | symbol, receiver: any): any {
console.log(`代理:读取属性 ${String(key)}`);
const dep = deps.get(key) || new Dep();
deps.set(key, dep);
// 模拟Vue的依赖收集
if (typeof window !== 'undefined' && window['activeEffect']) {
dep.depend(window['activeEffect']);
}
return Reflect.get(target, key, receiver);
},
set(target: T, key: string | symbol, value: any, receiver: any): boolean {
console.log(`代理:设置属性 ${String(key)} = ${value}`);
const result = Reflect.set(target, key, value, receiver);
// 触发依赖更新
const dep = deps.get(key);
if (dep) {
dep.notify();
}
return result;
}
});
}
说明:
Dep
类管理依赖(视图更新函数),depend
注册依赖,notify
触发更新。reactive
函数创建ES6 Proxy,拦截get
和set
操作:get
:记录访问的属性并收集依赖(模拟Vue的effect
)。set
:更新属性并通知依赖更新。
window['activeEffect']
模拟Vue的effect上下文,用于收集当前运行的视图更新函数。
3. 实现客户端 (component.ts)
typescript
import { reactive } from './reactive';
interface Data {
count: number;
message: string;
}
export class VueComponent {
private data: Data;
constructor() {
// 创建响应式数据
this.data = reactive({
count: 0,
message: '欢迎使用Vue'
});
}
// 模拟视图渲染(依赖)
render(): void {
// 模拟Vue的effect,设置当前依赖
(window as any).activeEffect = () => this.updateView();
console.log('开始渲染视图');
// 访问数据,触发get拦截
const count = this.data.count;
const message = this.data.message;
console.log(`视图渲染:count=${count}, message="${message}"`);
(window as any).activeEffect = null; // 清理effect
}
// 模拟视图更新
private updateView(): void {
console.log('视图更新:重新渲染');
console.log(`当前数据:count=${this.data.count}, message="${this.data.message}"`);
}
// 修改数据,触发set拦截
updateData(key: keyof Data, value: any): void {
console.log(`Vue组件:更新数据 ${key}=${value}`);
this.data[key] = value;
}
getData(): Data {
return this.data;
}
}
说明:
VueComponent
模拟Vue组件,持有响应式数据(通过reactive
创建)。render
模拟视图渲染,访问数据触发get
拦截,注册依赖。updateData
修改数据,触发set
拦截,自动调用updateView
。
4. 运行示例 (main.ts)
typescript
import { VueComponent } from './component';
// 创建Vue组件
const component = new VueComponent();
// 首次渲染(收集依赖)
component.render();
// 更新数据(触发响应式更新)
component.updateData('count', 1);
component.updateData('message', '已更新');
// 再次访问数据(不触发渲染,仅读取)
console.log(`直接访问数据:count=${component.getData().count}, message="${component.getData().message}"`);
5. 编译与运行
bash
npx tsc
node dist/main.js
运行后,控制台输出类似:
ini
开始渲染视图
代理:读取属性 count
依赖已注册
代理:读取属性 message
依赖已注册
视图渲染:count=0, message="欢迎使用Vue"
Vue组件:更新数据 count=1
代理:设置属性 count=1
通知依赖更新
视图更新:重新渲染
当前数据:count=1, message="欢迎使用Vue"
Vue组件:更新数据 message=已更新
代理:设置属性 message=已更新
通知依赖更新
视图更新:重新渲染
当前数据:count=1, message="已更新"
直接访问数据:count=1, message="已更新"
代理:读取属性 count
代理:读取属性 message
解释输出:
render
触发get
拦截,收集依赖(updateView
)。- 修改
count
和message
触发set
拦截,通知依赖,自动调用updateView
更新视图。 - 直接访问数据只触发
get
,不触发渲染,模拟Vue的响应式机制。 - 代理透明地拦截数据操作,客户端(
VueComponent
)无需感知代理逻辑。
总结
代理模式的优点在于其访问控制、功能增强、透明性和动态性。代理可以拦截操作,增强数据安全性;支持添加响应式、日志或缓存逻辑;客户端直接操作数据,无需感知代理;ES6 Proxy提供灵活的拦截机制。该模式特别适用于响应式系统、访问控制、日志/监控和延迟加载场景,如Vue.js的响应式数据绑定、状态管理、API调用代理和数据库访问控制。