阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 惰性初始化模式(Lazy Initialization)的实现技巧

惰性初始化模式(Lazy Initialization)的实现技巧

作者:陈川 阅读数:20811人阅读 分类: JavaScript

惰性初始化模式是一种延迟对象创建或计算的设计模式,直到真正需要时才进行初始化。这种模式特别适合资源密集型操作或需要按需加载的场景,能够有效提升性能并减少不必要的内存消耗。

基本实现原理

惰性初始化的核心思想是通过代理层控制初始化时机。在JavaScript中,通常通过以下方式实现:

  1. 使用标志位记录初始化状态
  2. 在首次访问时执行初始化
  3. 缓存初始化结果供后续使用

最简单的实现方式是使用闭包保存状态:

function createLazyObject(initializer) {
  let cached = null;
  return {
    get() {
      if (cached === null) {
        cached = initializer();
      }
      return cached;
    }
  };
}

// 使用示例
const heavyObject = createLazyObject(() => {
  console.log('执行耗时初始化');
  return { data: '大型数据' };
});

console.log(heavyObject.get()); // 首次调用会初始化
console.log(heavyObject.get()); // 直接返回缓存

属性描述符实现

ES5引入的属性描述符可以更优雅地实现惰性初始化:

function lazyProperty(obj, prop, initializer) {
  Object.defineProperty(obj, prop, {
    configurable: true,
    enumerable: true,
    get() {
      const value = initializer.call(this);
      Object.defineProperty(this, prop, {
        value,
        writable: true,
        configurable: true,
        enumerable: true
      });
      return value;
    }
  });
}

// 使用示例
const api = {};
lazyProperty(api, 'userData', function() {
  console.log('获取用户数据');
  return fetch('/user-data').then(res => res.json());
});

api.userData.then(data => console.log(data)); // 首次访问触发获取

类中的惰性初始化

在ES6类中实现惰性属性有多种方式:

方式一:Getter/Setter

class HeavyCalculator {
  constructor() {
    this._result = null;
  }
  
  get result() {
    if (this._result === null) {
      console.log('执行复杂计算');
      this._result = this._compute();
    }
    return this._result;
  }
  
  _compute() {
    // 模拟耗时计算
    let sum = 0;
    for(let i = 0; i < 1000000; i++) {
      sum += Math.sqrt(i);
    }
    return sum;
  }
}

const calc = new HeavyCalculator();
console.log(calc.result); // 首次访问触发计算
console.log(calc.result); // 直接返回缓存

方式二:Proxy代理

function lazyInitClass(Class) {
  return new Proxy(Class, {
    construct(target, args) {
      const instance = Reflect.construct(target, args);
      return new Proxy(instance, {
        get(obj, prop) {
          if (prop in obj && typeof obj[prop] === 'function') {
            return obj[prop].bind(obj);
          }
          
          if (prop.startsWith('_lazy_') && !(prop in obj)) {
            const initProp = prop.slice(6);
            if (initProp in obj && typeof obj[`_init_${initProp}`] === 'function') {
              obj[prop] = obj[`_init_${initProp}`]();
            }
          }
          
          return obj[prop];
        }
      });
    }
  });
}

// 使用示例
const LazyUser = lazyInitClass(class User {
  _init_profile() {
    console.log('加载用户资料');
    return { name: '张三', age: 30 };
  }
  
  get profile() {
    return this._lazy_profile;
  }
});

const user = new LazyUser();
console.log(user.profile); // 首次访问触发初始化
console.log(user.profile); // 直接返回缓存

异步惰性初始化

对于需要异步加载的资源,可以使用Promise实现:

class AsyncLazyLoader {
  constructor(loader) {
    this._loader = loader;
    this._promise = null;
    this._value = null;
  }
  
  get() {
    if (this._value !== null) return Promise.resolve(this._value);
    if (this._promise !== null) return this._promise;
    
    this._promise = this._loader().then(value => {
      this._value = value;
      return value;
    });
    
    return this._promise;
  }
  
  // 强制刷新
  refresh() {
    this._promise = null;
    this._value = null;
    return this.get();
  }
}

// 使用示例
const imageLoader = new AsyncLazyLoader(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('图片加载完成');
      resolve('图片数据');
    }, 1000);
  });
});

imageLoader.get().then(data => console.log(data)); // 首次加载
imageLoader.get().then(data => console.log(data)); // 使用缓存

应用场景与优化

图片懒加载

class LazyImage {
  constructor(placeholderSrc, realSrc) {
    this.img = new Image();
    this.img.src = placeholderSrc;
    this.realSrc = realSrc;
    this.loaded = false;
    
    // 交叉观察器实现懒加载
    this.observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting && !this.loaded) {
          this._loadRealImage();
          this.observer.unobserve(this.img);
        }
      });
    });
    
    this.observer.observe(this.img);
  }
  
  _loadRealImage() {
    this.loaded = true;
    this.img.src = this.realSrc;
  }
}

模块懒加载

结合动态import实现按需加载:

const componentLoaders = {
  Chart: () => import('./components/Chart.js'),
  Map: () => import('./components/Map.js'),
  Calendar: () => import('./components/Calendar.js')
};

class LazyComponentLoader {
  constructor() {
    this._components = {};
  }
  
  async load(name) {
    if (this._components[name]) {
      return this._components[name];
    }
    
    if (componentLoaders[name]) {
      const module = await componentLoaders[name]();
      this._components[name] = module.default;
      return module.default;
    }
    
    throw new Error(`未知组件: ${name}`);
  }
}

// 使用示例
const loader = new LazyComponentLoader();
document.getElementById('show-chart').addEventListener('click', async () => {
  const Chart = await loader.load('Chart');
  new Chart().render();
});

性能考量与陷阱

  1. 内存泄漏风险:缓存的对象如果不及时释放可能导致内存泄漏

    // 解决方案:提供清理方法
    class LazyCache {
      constructor() {
        this._cache = new Map();
      }
      
      get(key, initializer) {
        if (!this._cache.has(key)) {
          this._cache.set(key, initializer());
        }
        return this._cache.get(key);
      }
      
      clear(key) {
        this._cache.delete(key);
      }
      
      clearAll() {
        this._cache.clear();
      }
    }
    
  2. 并发初始化问题:多个地方同时触发初始化可能导致重复计算

    // 使用Promise解决并发问题
    function createLazyAsync(initializer) {
      let promise = null;
      return () => {
        if (!promise) {
          promise = initializer().finally(() => {
            // 初始化完成后允许重新加载
            promise = null;
          });
        }
        return promise;
      };
    }
    
  3. 测试复杂性增加:惰性初始化可能使测试更困难

    // 测试时可通过强制初始化方法解决
    class TestableLazy {
      constructor() {
        this._initialized = false;
      }
      
      get data() {
        if (!this._initialized) {
          this._initialize();
        }
        return this._data;
      }
      
      _initialize() {
        this._data = /* 初始化逻辑 */;
        this._initialized = true;
      }
      
      // 测试专用方法
      __testOnlyInitialize() {
        this._initialize();
      }
    }
    

高级模式:多级缓存

对于需要多层缓存的场景,可以结合WeakMap和Map实现:

class MultiLevelCache {
  constructor() {
    this._weakCache = new WeakMap(); // 第一级:弱引用缓存
    this._strongCache = new Map();  // 第二级:强引用缓存
    this._maxSize = 100;            // 强引用缓存最大大小
  }
  
  get(key, initializer) {
    // 尝试从弱引用缓存获取
    let value = this._weakCache.get(key);
    if (value !== undefined) return value;
    
    // 尝试从强引用缓存获取
    value = this._strongCache.get(key);
    if (value !== undefined) {
      // 提升到弱引用缓存
      this._weakCache.set(key, value);
      return value;
    }
    
    // 初始化并缓存
    value = initializer();
    this._weakCache.set(key, value);
    this._strongCache.set(key, value);
    
    // 控制强引用缓存大小
    if (this._strongCache.size > this._maxSize) {
      const oldestKey = this._strongCache.keys().next().value;
      this._strongCache.delete(oldestKey);
    }
    
    return value;
  }
}

与其它模式的结合

惰性初始化 + 工厂模式

class LazyFactory {
  constructor(factoryFn) {
    this._factory = factoryFn;
    this._instances = new Map();
  }
  
  getInstance(key) {
    if (!this._instances.has(key)) {
      this._instances.set(key, {
        initialized: false,
        value: null,
        promise: null
      });
    }
    
    const record = this._instances.get(key);
    
    if (record.initialized) {
      return Promise.resolve(record.value);
    }
    
    if (record.promise) {
      return record.promise;
    }
    
    record.promise = this._factory(key)
      .then(value => {
        record.value = value;
        record.initialized = true;
        return value;
      });
    
    return record.promise;
  }
}

// 使用示例
const userFactory = new LazyFactory(userId => 
  fetch(`/api/users/${userId}`).then(res => res.json())
);

userFactory.getInstance(123).then(user => console.log(user));

惰性初始化 + 装饰器模式

在支持装饰器的环境中:

function lazy(target, name, descriptor) {
  const { get, set } = descriptor;
  
  if (get) {
    descriptor.get = function() {
      const value = get.call(this);
      Object.defineProperty(this, name, {
        value,
        enumerable: true,
        configurable: true,
        writable: true
      });
      return value;
    };
  }
  
  return descriptor;
}

class DecoratorExample {
  @lazy
  get expensiveData() {
    console.log('计算数据');
    return computeExpensiveData();
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌