如何实现惰性加载的Set?
问题
现有代码:
new Set(getObjects())
getObjects 返回一个可迭代对象,每次迭代都会创建一个新对象,但 Set 会立即遍历整个可迭代对象,把所有新对象都添加进去。
如何实现仅在需要新对象时才去获取并添加的懒加载逻辑?
我尝试用 Proxy 包装 Set,但发现当以 receiver 作为 this 调用函数时,Proxy 的处理器并没有被触发。以下是我的尝试代码:
const getEntitySet = (nativeEntityCollection) => { const cache = new Set; let src = { [Symbol.iterator]: ((itr) => { return () => itr; })(nativeEntityCollection[Symbol.iterator]()), ref: nativeEntityCollection }; let addAll = () => { for (const entity of src) { cache.add(entity) } done() }; let generate = function*() { yield* cache; for (const entity of src) { cache.add(entity); yield entity; } done() }; const done = () => { src = null; addAll = null; generate = Reflect.get(cache, Symbol.iterator).bind(cache) }; const generateRef = () => generate(); return new Proxy(cache, { get(set, prop) { if (prop === Symbol.iterator) { return generateRef; } addAll?.(); return Reflect.get(cache, prop); } }); };
解决方案
你的核心问题在于:Proxy 的 get 陷阱里,除了 Symbol.iterator 外的所有属性访问都会触发 addAll(),直接把所有元素加载完毕,违背了懒加载的初衷;另外,当迭代器函数被调用后,内部的操作并没有经过 Proxy,导致后续依赖 Set 内部状态的操作无法继续懒加载。
方案一:自定义懒加载 Set 类
通过实现自定义类模拟 Set 接口,能精准控制每一个操作的懒加载逻辑,完全规避 Proxy 的陷阱问题:
class LazySet { constructor(iterable) { this.cache = new Set(); this.source = iterable[Symbol.iterator](); this.isExhausted = false; } // 迭代时懒加载元素 *[Symbol.iterator]() { yield* this.cache; if (this.isExhausted) return; let result; while (!(result = this.source.next()).done) { const item = result.value; this.cache.add(item); yield item; } this.isExhausted = true; this.source = null; } // has 方法:缓存未命中时继续迭代查找 has(item) { if (this.cache.has(item)) return true; if (this.isExhausted) return false; let result; while (!(result = this.source.next()).done) { const current = result.value; this.cache.add(current); if (current === item) return true; } this.isExhausted = true; this.source = null; return false; } // size 属性:需获取总数时才加载全部元素 get size() { if (!this.isExhausted) { this._exhaustAll(); } return this.cache.size; } // 内部方法:一次性加载所有剩余元素 _exhaustAll() { if (this.isExhausted) return; let result; while (!(result = this.source.next()).done) { this.cache.add(result.value); } this.isExhausted = true; this.source = null; } // 兼容原生 Set 的其他方法 add(item) { this.cache.add(item); return this; } delete(item) { return this.cache.delete(item); } clear() { this.cache.clear(); this.isExhausted = false; // 若需重置源迭代器,可在此重新获取 // this.source = this.originalIterable[Symbol.iterator](); } forEach(callback, thisArg) { for (const item of this) { callback.call(thisArg, item, item, this); } } } // 使用示例 const lazySet = new LazySet(getObjects()); // 迭代时逐个加载元素 for (const obj of lazySet) { console.log(obj); } // has 方法会在缓存未命中时继续加载查找 console.log(lazySet.has(someObj));
方案二:改进 Proxy 实现
如果坚持用 Proxy,需避免非必要操作触发全量加载,同时重写关键方法实现懒加载:
const getEntitySet = (nativeEntityCollection) => { const cache = new Set(); const source = nativeEntityCollection[Symbol.iterator](); let isExhausted = false; // 懒加载迭代器 const lazyIterator = function*() { yield* cache; if (isExhausted) return; let result; while (!(result = source.next()).done) { const item = result.value; cache.add(item); yield item; } isExhausted = true; }; // 全量加载剩余元素 const exhaustAll = () => { if (isExhausted) return; let result; while (!(result = source.next()).done) { cache.add(result.value); } isExhausted = true; }; return new Proxy(cache, { get(target, prop) { if (prop === Symbol.iterator) { return lazyIterator; } // size 属性需全量加载 if (prop === 'size') { exhaustAll(); return Reflect.get(target, prop); } // 重写 has 方法实现懒加载查找 if (prop === 'has') { return (item) => { if (target.has(item)) return true; if (isExhausted) return false; let result; while (!(result = source.next()).done) { const current = result.value; target.add(current); if (current === item) return true; } isExhausted = true; return false; }; } // forEach 需遍历全部元素,先全量加载 if (prop === 'forEach') { exhaustAll(); return Reflect.get(target, prop); } return Reflect.get(target, prop); } }); };
内容的提问来源于stack exchange,提问作者Worldwidebrine
相关产品推荐
相关产品推荐

