JavaScript Proxy 可实现延迟加载:首次读取属性时执行 getter 并缓存结果到目标对象,后续直接返回缓存值;异步场景需缓存 Promise 本身以避免重复请求。

可以利用 JavaScript 的 Proxy 拦截对象属性访问,在首次读取某个属性时才真正计算或获取值,之后缓存结果——这就是“延迟加载”(Lazy Loading)的核心逻辑。
核心思路:用 Proxy 拦截 get 操作
通过 handler.get 拦截对目标对象属性的读取。若该属性尚未初始化,就执行一次计算/加载,并将结果缓存到目标对象上;后续再读取时直接返回缓存值。
- 目标对象本身可是一个空对象或仅含懒加载描述符的结构
- 缓存行为必须是“写入目标对象”,否则下次仍会触发拦截
- 需避免重复初始化,尤其在多线程(如 Promise 异步)场景下要注意竞态(简单场景可忽略,复杂场景建议加锁或用
Promise缓存)
基础实现:同步延迟加载
适用于属性值可通过纯函数快速生成的场景,例如格式化数据、派生字段等:
阿里百炼大模型平台多人共享账号用量统计代理服务。解决公司共用账号时无法区分个人用量的问题。适用场景:1)部署用量统计代理;2)统计多人Token用量和调用次数;3)管理大模型API调用配额。
function lazyObject(descriptor) {
const target = {};
return new Proxy(target, {
get(obj, prop) {
if (prop in obj) return obj[prop]; // 已缓存,直接返回
if (prop in descriptor) {
const getter = descriptor[prop];
obj[prop] = getter(); // 执行并缓存
return obj[prop];
}
return undefined;
}
});
}
// 使用示例
const user = lazyObject({
fullName: () => {
console.log('→ 正在计算 fullName');
return 'Alice Smith';
},
initials: () => {
console.log('→ 正在计算 initials');
return 'AS';
}
});
console.log(user.fullName); // → 正在计算 fullName,输出 "Alice Smith"
console.log(user.fullName); // 直接输出 "Alice Smith"(无日志)
进阶支持:异步延迟加载(Promise 缓存)
当属性需要异步获取(如 API 请求),应缓存 Promise 本身,而非等待 resolve 后再赋值,避免多次触发请求:
- 首次访问返回一个 Promise,并立即发起异步操作
- 把该 Promise 存到目标对象上,后续访问直接复用这个 Promise
- 无需手动处理 `.then()`,使用者 await 即可,语义自然
function asyncLazyObject(descriptor) {
const target = {};
return new Proxy(target, {
get(obj, prop) {
if (prop in obj) return obj[prop];
if (prop in descriptor) {
const loader = descriptor[prop];
const promise = loader();
obj[prop] = promise; // 缓存 Promise,不是结果
return promise;
}
return undefined;
}
});
}
// 使用示例
const data = asyncLazyObject({
posts: () => fetch('/api/posts').then(r => r.json()),
config: () => Promise.resolve({ theme: 'dark' })
});
data.posts.then(posts => console.log(posts.length)); // 发起请求
data.posts.then(posts => console.log(posts.length)); // 复用同一 Promise,不重复请求
注意事项与边界情况
实际使用中需留意以下几点:
-
不可枚举性:Proxy 默认不会暴露懒加载属性在
Object.keys()或for...in中,如需枚举,需配合ownKeys和getOwnPropertyDescriptor拦截 -
setter 干预:如果允许外部写入(如
obj.x = 123),需在set拦截中跳过懒加载逻辑,或清空对应缓存 - 原型链影响:Proxy 不代理原型上的属性,确保懒加载逻辑只作用于自身属性
-
调试友好性:可在
get中加入console.warn提示未缓存访问,便于定位性能问题

















