You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现惰性加载的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 11:20:59