Proxy 模式
用一个代理对象拦截对目标对象的访问,在不修改目标对象的前提下控制或扩展其行为。与 Decorator 的区别:Decorator 增强行为,Proxy 通常用于透明转发、访问控制或懒加载。
定义
Proxy 模式用一个代理对象包装真实对象,拦截对其属性和方法的访问。调用方看到的是代理,但实际行为可以在代理层被修改、拦截或转发。
与 Decorator、Adapter 的核心区别
在 TypeScript 框架里最容易混淆的场景:所有三种模式都是"包装另一个对象",区分点在于为什么包装。
vercel/ai 中的两种 Proxy 变体
轻量 Proxy:V3 → V4(as-language-model-v4.ts:L19,25 行)
return new Proxy(v3Model, {
get(target, prop) {
if (prop === 'specificationVersion') return 'v4'; // 唯一修改
return target[prop]; // 全部透传
},
}) as unknown as LanguageModelV4;
只拦截 1 个属性(specificationVersion),其余全部透传。零运行时开销,V3 和 V4 数据结构完全兼容,只是类型名不同——纯类型系统问题,Proxy 只是为了让 TypeScript 编译器满意。
重量 Proxy:V2 → V3(as-language-model-v3.ts,103 行)
拦截 3 个属性:specificationVersion + doGenerate + doStream。
doGenerate 需要做真实的结构转换——V2 和 V3 的 usage 字段结构不同:
// V2 usage(平铺)
{ inputTokens: number, outputTokens: number }
// V3 usage(嵌套,更细粒度)
{ inputTokens: { total, noCache, cacheRead, cacheWrite }, outputTokens: { total, text, reasoning } }
doStream 有一个 P1 技术债(as-language-model-v3.ts:L72):stream chunk 的 default 分支直接强制转换(chunk as LanguageModelV3StreamPart),不做事件映射。V2 中不存在的 stream 事件类型(如 V3 新增的 reasoning 系列)会被静默传递错误格式数据。
结论:V2→V3 是真正的数据模型演化(usage 结构重构),V3→V4 是接口合同重构(返回类型可寻址化),两次升级解决的是完全不同的问题。
TypeScript Proxy 的类型弱点
as unknown as TargetType 是 JavaScript Proxy 在 TypeScript 中的系统性代价——编译器无法自动推断 Proxy 的返回类型符合目标接口,必须强制转换。这在两个变体中都出现,是 Proxy 模式在 TypeScript 里的固有局限,不是设计失误。