五、微前端 - 高级特性与优化 🚀

232 阅读6分钟

第五章:高级特性与优化 🚀

5.1 沙箱机制深度解析

5.1.1 沙箱机制概述

沙箱机制是微前端架构中的核心技术,它确保不同子应用之间的 JavaScript 执行环境相互隔离,防止全局变量污染和冲突。

沙箱机制的核心目标

  • 隔离性:不同子应用的 JavaScript 执行环境完全隔离
  • 安全性:防止恶意代码影响其他应用
  • 性能:最小化沙箱带来的性能开销
  • 兼容性:支持各种 JavaScript 特性和 API
5.1.2 快照沙箱(SnapshotSandbox)

快照沙箱是 qiankun 中最基础的沙箱实现,通过记录和恢复 window 对象的状态来实现隔离。

快照沙箱实现

//
export class SnapshotSandbox {
  private windowSnapshot: Record<string, any> = {};
  private modifyPropsMap: Record<string, any> = {};
  private running = false;

  constructor() {
    this.init();
  }

  private init() {
    // 初始化时记录 window 的初始状态
    this.windowSnapshot = {};
    for (const prop in window) {
      this.windowSnapshot[prop] = window[prop];
    }
  }

  // 激活沙箱
  active() {
    if (this.running) {
      return;
    }

    this.running = true;

    // 恢复之前修改的属性
    for (const prop in this.modifyPropsMap) {
      window[prop] = this.modifyPropsMap[prop];
    }
  }

  // 失活沙箱
  inactive() {
    if (!this.running) {
      return;
    }

    this.running = false;

    // 记录当前修改的属性
    for (const prop in window) {
      if (window[prop] !== this.windowSnapshot[prop]) {
        this.modifyPropsMap[prop] = window[prop];
        // 恢复原始状态
        window[prop] = this.windowSnapshot[prop];
      }
    }
  }

  // 销毁沙箱
  destroy() {
    this.inactive();
    this.windowSnapshot = {};
    this.modifyPropsMap = {};
  }
}

快照沙箱的优缺点

优点

  • 实现简单,兼容性好
  • 对现有代码影响小
  • 支持所有 JavaScript 特性

缺点

  • 只能同时运行一个子应用
  • 性能开销较大
  • 无法完全隔离全局变量

使用示例

// 1. 创建沙箱实例
const sandbox = new SnapshotSandbox();

// 2. 激活沙箱(子应用启动时)
sandbox.active();

// 此时子应用可以正常使用 window 对象
// 例如:子应用代码中
window.myAppName = "sub-app-1";
window.myAppConfig = { version: "1.0.0" };

// 3. 失活沙箱(子应用卸载时)
sandbox.inactive();
// 此时 window 对象会恢复到沙箱激活前的状态
// window.myAppName 和 window.myAppConfig 会被清除

// 4. 再次激活沙箱(子应用重新启动时)
sandbox.active();
// 此时会恢复之前修改的属性
// window.myAppName 和 window.myAppConfig 会恢复为 'sub-app-1' 和 { version: '1.0.0' }

// 5. 销毁沙箱(不再需要时)
sandbox.destroy();

完整使用场景示例

// 微前端框架中的使用
class MicroApp {
  private sandbox: SnapshotSandbox;
  private appName: string;

  constructor(appName: string) {
    this.appName = appName;
    // 为每个子应用创建独立的沙箱实例
    this.sandbox = new SnapshotSandbox();
  }

  // 启动子应用
  async mount() {
    // 激活沙箱
    this.sandbox.active();

    // 加载并执行子应用代码
    const appCode = await this.loadAppCode();
    eval(appCode); // 在实际场景中,应该使用更安全的方式执行代码

    console.log(`子应用 ${this.appName} 已启动`);
  }

  // 卸载子应用
  async unmount() {
    // 失活沙箱,恢复 window 对象
    this.sandbox.inactive();

    console.log(`子应用 ${this.appName} 已卸载`);
  }

  // 销毁子应用
  destroy() {
    // 销毁沙箱
    this.sandbox.destroy();
    console.log(`子应用 ${this.appName} 已销毁`);
  }

  private async loadAppCode(): Promise<string> {
    // 加载子应用代码的逻辑
    return "";
  }
}

// 使用示例
const app1 = new MicroApp("app1");
const app2 = new MicroApp("app2");

// 启动第一个应用
await app1.mount();
// 此时 window 对象被 app1 修改

// 卸载第一个应用
await app1.unmount();
// 此时 window 对象恢复到初始状态

// 启动第二个应用
await app2.mount();
// 此时 window 对象被 app2 修改,但不会受到 app1 的影响

// 卸载第二个应用
await app2.unmount();
// 此时 window 对象恢复到初始状态

注意事项

  1. 单实例限制:快照沙箱只能同时运行一个子应用,如果需要同时运行多个应用,应该使用 ProxySandbox
  2. 生命周期管理:确保在子应用卸载时调用 inactive(),避免全局变量污染
  3. 性能考虑:快照沙箱需要遍历整个 window 对象,性能开销较大,适合单应用场景
  4. 兼容性:快照沙箱兼容所有浏览器,包括不支持 Proxy 的旧浏览器
5.1.3 代理沙箱(ProxySandbox)

代理沙箱使用 ES6 的 Proxy 特性,通过代理 window 对象来实现更精细的隔离控制。

代理沙箱实现

// src/sandbox/ProxySandbox.ts
export class ProxySandbox {
  private proxy: Window;
  private running = false;
  private sandboxRunning = false;

  constructor() {
    this.proxy = this.createProxy();
  }

  private createProxy(): Window {
    const rawWindow = window;
    const fakeWindow = {} as Window;

    /**
     * 问题 1:Proxy 的第一个参数应该是 fakeWindow 还是 rawWindow?
     *
     * 答案:应该是 fakeWindow
     *
     * 原因:
     * 1. 所有子应用的修改都应该记录到 fakeWindow 中,实现隔离
     * 2. 当访问属性时,优先从 fakeWindow 获取(子应用自己的属性)
     * 3. 如果 fakeWindow 中没有,才从 rawWindow 获取(全局共享的属性)
     *
     * 注意:也有实现使用 new Proxy(window, ...),但那样需要更复杂的拦截逻辑
     */
    return new Proxy(fakeWindow, {
      get(target, key) {
        // target 指向 fakeWindow
        // 优先从代理对象(fakeWindow)获取
        if (key in target) {
          const value = target[key];

          /**
           * 问题 2:如果 value 是 function,应该 bind 到 fakeWindow 还是 rawWindow?
           *
           * 答案:应该 bind 到 fakeWindow
           *
           * 原因:
           * 1. 如果函数是子应用自己定义的(存储在 fakeWindow 中),
           *    应该 bind 到 fakeWindow,这样函数内部的 this 指向 fakeWindow
           * 2. 例如:window.myFunc = function() { return this.myVar; }
           *    调用时,this 应该是 fakeWindow,而不是 rawWindow
           */
          if (typeof value === "function") {
            return value.bind(fakeWindow);
          }
          return value;
        }

        // 从原始 window 获取
        const value = rawWindow[key];

        /**
         * 对于从 rawWindow 获取的函数,应该 bind 到 rawWindow
         *
         * 原因:
         * 1. 这些是浏览器原生的 API(如 addEventListener、setTimeout 等)
         * 2. 它们需要在真实的 window 上下文中执行
         * 3. 例如:window.addEventListener 需要在真实的 window 上添加事件监听器
         */
        if (typeof value === "function") {
          return value.bind(rawWindow);
        }

        return value;
      },

      set(target, key, value) {
        if (this.sandboxRunning) {
          target[key] = value;
        }
        return true;
      },

      has(target, key) {
        return key in target || key in rawWindow;
      },

      deleteProperty(target, key) {
        if (this.sandboxRunning) {
          delete target[key];
        }
        return true;
      },

      ownKeys(target) {
        return Reflect.ownKeys(rawWindow);
      },

      getOwnPropertyDescriptor(target, key) {
        return Reflect.getOwnPropertyDescriptor(rawWindow, key);
      },

      defineProperty(target, key, descriptor) {
        if (this.sandboxRunning) {
          return Reflect.defineProperty(target, key, descriptor);
        }
        return true;
      },
    });
  }

  // 激活沙箱
  active() {
    if (this.running) {
      return;
    }

    this.running = true;
    this.sandboxRunning = true;
  }

  // 失活沙箱
  inactive() {
    if (!this.running) {
      return;
    }

    this.running = false;
    this.sandboxRunning = false;
  }

  // 获取代理对象
  getProxy() {
    return this.proxy;
  }
}

关键问题详解

  1. 为什么 Proxy 的第一个参数是 fakeWindow?

    • new Proxy(fakeWindow, ...) 表示代理的是 fakeWindow 对象
    • 所有对代理对象的操作(get/set)都会作用在 fakeWindow
    • 这样确保了子应用的修改都记录在 fakeWindow 中,不会污染 rawWindow
    • 当访问属性时,优先从 fakeWindow 获取,实现了隔离
  2. 函数绑定的两种场景

    场景 A:从 fakeWindow 获取的函数(子应用定义的)

    // 子应用代码:window.myFunc = function() { return this.myVar; }
    // 这个函数存储在 fakeWindow 中
    // 应该 bind 到 fakeWindow,这样 this.myVar 访问的是 fakeWindow.myVar
    if (key in target) {
      // target 是 fakeWindow
      const value = target[key];
      if (typeof value === "function") {
        return value.bind(fakeWindow); // ✅ 正确
      }
    }
    

    场景 B:从 rawWindow 获取的函数(浏览器原生 API)

    // 浏览器原生 API:window.addEventListener、window.setTimeout 等
    // 这些函数需要在真实的 window 上下文中执行
    const value = rawWindow[key];
    if (typeof value === "function") {
      return value.bind(rawWindow); // ✅ 正确
    }
    
  3. 两种实现方式对比

    方式一:new Proxy(fakeWindow, ...)(当前实现)

    • ✅ 优点:逻辑清晰,所有修改都在 fakeWindow 中
    • ✅ 优点:隔离性更好
    • ⚠️ 注意:需要区分函数来源,分别绑定

    方式二:new Proxy(window, ...)(另一种实现)

    • ✅ 优点:可以直接访问 window 的所有属性
    • ⚠️ 缺点:需要更复杂的拦截逻辑,将修改重定向到 fakeWindow
    • ⚠️ 缺点:需要维护 updatedValueSet 来跟踪哪些属性被修改过

代理沙箱的优缺点

优点

  • 可以同时运行多个子应用
  • 性能更好
  • 隔离性更强

缺点

  • 需要 ES6 Proxy 支持
  • 实现复杂
  • 某些特殊场景可能有兼容性问题
5.1.4 沙箱选择策略

沙箱选择决策树

// src/sandbox/SandboxSelector.ts
export class SandboxSelector {
  static selectSandbox(options: {
    strictStyleIsolation?: boolean;
    experimentalStyleIsolation?: boolean;
    singular?: boolean;
  }) {
    const { strictStyleIsolation, experimentalStyleIsolation, singular } =
      options;

    // 如果启用了严格样式隔离,使用代理沙箱
    if (strictStyleIsolation || experimentalStyleIsolation) {
      return new ProxySandbox();
    }

    // 如果只能运行一个应用,使用快照沙箱
    if (singular) {
      return new SnapshotSandbox();
    }

    // 默认使用代理沙箱
    return new ProxySandbox();
  }
}

5.2 性能优化策略

5.2.1 预加载机制

预加载机制可以在空闲时间预先加载子应用资源,提升用户体验。

预加载实现

// src/performance/Preloader.ts
export class Preloader {
  private loadedApps = new Set<string>();
  private loadingApps = new Set<string>();
  private preloadQueue: string[] = [];

  constructor(private appConfigs: any[]) {
    this.setupIdleCallback();
  }

  private setupIdleCallback() {
    // 使用 requestIdleCallback 在浏览器空闲时预加载
    if ("requestIdleCallback" in window) {
      requestIdleCallback(() => {
        this.preloadNextApp();
      });
    } else {
      // 降级处理
      setTimeout(() => {
        this.preloadNextApp();
      }, 100);
    }
  }

  private async preloadNextApp() {
    if (this.preloadQueue.length === 0) {
      return;
    }

    const appName = this.preloadQueue.shift()!;
    if (this.loadedApps.has(appName) || this.loadingApps.has(appName)) {
      this.preloadNextApp();
      return;
    }

    this.loadingApps.add(appName);

    try {
      const appConfig = this.appConfigs.find(
        (config) => config.name === appName
      );
      if (appConfig) {
        await this.preloadApp(appConfig);
        this.loadedApps.add(appName);
      }
    } catch (error) {
      console.error(`Failed to preload app ${appName}:`, error);
    } finally {
      this.loadingApps.delete(appName);
      this.preloadNextApp();
    }
  }

  private async preloadApp(appConfig: any) {
    // 预加载应用脚本
    const script = document.createElement("script");
    script.src = appConfig.entry;
    script.async = true;

    return new Promise((resolve, reject) => {
      script.onload = resolve;
      script.onerror = reject;
      document.head.appendChild(script);
    });
  }

  // 添加应用到预加载队列
  addToPreloadQueue(appName: string) {
    if (!this.preloadQueue.includes(appName)) {
      this.preloadQueue.push(appName);
    }
  }

  // 检查应用是否已预加载
  isAppPreloaded(appName: string): boolean {
    return this.loadedApps.has(appName);
  }
}
5.2.2 资源缓存策略

资源缓存实现

// src/performance/ResourceCache.ts
export class ResourceCache {
  private cache = new Map<string, any>();
  private maxSize = 50; // 最大缓存数量
  private ttl = 5 * 60 * 1000; // 5分钟过期时间

  // 缓存资源
  cacheResource(key: string, resource: any) {
    // 如果缓存已满,删除最旧的资源
    if (this.cache.size >= this.maxSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    this.cache.set(key, {
      data: resource,
      timestamp: Date.now(),
    });
  }

  // 获取缓存资源
  getResource(key: string): any {
    const cached = this.cache.get(key);
    if (!cached) {
      return null;
    }

    // 检查是否过期
    if (Date.now() - cached.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }

    return cached.data;
  }

  // 清理过期缓存
  cleanExpiredCache() {
    const now = Date.now();
    for (const [key, value] of this.cache.entries()) {
      if (now - value.timestamp > this.ttl) {
        this.cache.delete(key);
      }
    }
  }

  // 清空所有缓存
  clearCache() {
    this.cache.clear();
  }
}
5.2.3 懒加载实现

懒加载实现

// src/performance/LazyLoader.ts
export class LazyLoader {
  private observer: IntersectionObserver;
  private loadedComponents = new Set<string>();

  constructor() {
    this.observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            const target = entry.target as HTMLElement;
            const componentName = target.dataset.component;
            if (componentName && !this.loadedComponents.has(componentName)) {
              this.loadComponent(componentName, target);
            }
          }
        });
      },
      {
        rootMargin: "50px", // 提前50px开始加载
      }
    );
  }

  // 观察组件
  observeComponent(element: HTMLElement, componentName: string) {
    element.dataset.component = componentName;
    this.observer.observe(element);
  }

  // 停止观察组件
  unobserveComponent(element: HTMLElement) {
    this.observer.unobserve(element);
  }

  private async loadComponent(componentName: string, container: HTMLElement) {
    try {
      this.loadedComponents.add(componentName);

      // 动态导入组件
      const component = await import(`../components/${componentName}`);

      // 渲染组件
      if (component.default) {
        const instance = new component.default();
        container.appendChild(instance.render());
      }
    } catch (error) {
      console.error(`Failed to load component ${componentName}:`, error);
    }
  }
}

5.3 监控与调试

5.3.1 性能监控

性能监控实现

// src/monitoring/PerformanceMonitor.ts
export class PerformanceMonitor {
  private metrics: Map<string, any> = new Map();
  private observers: PerformanceObserver[] = [];

  constructor() {
    this.setupPerformanceObserver();
  }

  private setupPerformanceObserver() {
    // 监控资源加载性能
    if ("PerformanceObserver" in window) {
      const resourceObserver = new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
          this.recordMetric("resource", {
            name: entry.name,
            duration: entry.duration,
            size: entry.transferSize,
            type: entry.initiatorType,
          });
        });
      });

      resourceObserver.observe({ entryTypes: ["resource"] });
      this.observers.push(resourceObserver);

      // 监控长任务
      const longTaskObserver = new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
          this.recordMetric("longTask", {
            duration: entry.duration,
            startTime: entry.startTime,
          });
        });
      });

      longTaskObserver.observe({ entryTypes: ["longtask"] });
      this.observers.push(longTaskObserver);
    }
  }

  // 记录指标
  recordMetric(type: string, data: any) {
    const timestamp = Date.now();
    const metric = {
      type,
      data,
      timestamp,
    };

    if (!this.metrics.has(type)) {
      this.metrics.set(type, []);
    }

    this.metrics.get(type)!.push(metric);
  }

  // 获取性能报告
  getPerformanceReport() {
    const report: any = {};

    for (const [type, metrics] of this.metrics.entries()) {
      report[type] = {
        count: metrics.length,
        average: this.calculateAverage(metrics),
        max: this.calculateMax(metrics),
        min: this.calculateMin(metrics),
      };
    }

    return report;
  }

  private calculateAverage(metrics: any[]): number {
    if (metrics.length === 0) return 0;
    const sum = metrics.reduce((acc, metric) => acc + metric.data.duration, 0);
    return sum / metrics.length;
  }

  private calculateMax(metrics: any[]): number {
    if (metrics.length === 0) return 0;
    return Math.max(...metrics.map((metric) => metric.data.duration));
  }

  private calculateMin(metrics: any[]): number {
    if (metrics.length === 0) return 0;
    return Math.min(...metrics.map((metric) => metric.data.duration));
  }

  // 清理监控器
  destroy() {
    this.observers.forEach((observer) => observer.disconnect());
    this.observers = [];
    this.metrics.clear();
  }
}
5.3.2 错误监控

错误监控实现

// src/monitoring/ErrorMonitor.ts
export class ErrorMonitor {
  private errors: any[] = [];
  private maxErrors = 100;

  constructor() {
    this.setupErrorHandlers();
  }

  private setupErrorHandlers() {
    // 全局错误处理
    window.addEventListener("error", (event) => {
      this.recordError({
        type: "javascript",
        message: event.message,
        filename: event.filename,
        lineno: event.lineno,
        colno: event.colno,
        stack: event.error?.stack,
        timestamp: Date.now(),
      });
    });

    // Promise 错误处理
    window.addEventListener("unhandledrejection", (event) => {
      this.recordError({
        type: "promise",
        message: event.reason?.message || event.reason,
        stack: event.reason?.stack,
        timestamp: Date.now(),
      });
    });

    // 资源加载错误
    window.addEventListener(
      "error",
      (event) => {
        if (event.target !== window) {
          this.recordError({
            type: "resource",
            message: `Failed to load resource: ${(event.target as any).src}`,
            filename: (event.target as any).src,
            timestamp: Date.now(),
          });
        }
      },
      true
    );
  }

  // 记录错误
  recordError(error: any) {
    this.errors.push(error);

    // 限制错误数量
    if (this.errors.length > this.maxErrors) {
      this.errors.shift();
    }

    // 发送错误到服务器
    this.sendErrorToServer(error);
  }

  // 发送错误到服务器
  private async sendErrorToServer(error: any) {
    try {
      await fetch("/api/errors", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          ...error,
          userAgent: navigator.userAgent,
          url: window.location.href,
        }),
      });
    } catch (err) {
      console.error("Failed to send error to server:", err);
    }
  }

  // 获取错误报告
  getErrorReport() {
    const errorTypes = this.errors.reduce((acc, error) => {
      acc[error.type] = (acc[error.type] || 0) + 1;
      return acc;
    }, {});

    return {
      total: this.errors.length,
      types: errorTypes,
      recent: this.errors.slice(-10),
    };
  }
}
5.3.3 调试工具

调试工具实现

// src/debugging/Debugger.ts
export class MicroAppDebugger {
  private debugInfo: any = {};
  private isDebugMode = false;

  constructor() {
    this.setupDebugMode();
  }

  private setupDebugMode() {
    // 检查是否在调试模式
    this.isDebugMode =
      process.env.NODE_ENV === "development" ||
      window.location.search.includes("debug=true");

    if (this.isDebugMode) {
      this.createDebugPanel();
    }
  }

  private createDebugPanel() {
    const panel = document.createElement("div");
    panel.id = "micro-app-debug-panel";
    panel.style.cssText = `
      position: fixed;
      top: 10px;
      right: 10px;
      width: 300px;
      max-height: 500px;
      background: #fff;
      border: 1px solid #ccc;
      border-radius: 4px;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
      z-index: 9999;
      font-family: monospace;
      font-size: 12px;
      overflow-y: auto;
    `;

    panel.innerHTML = `
      <div style="padding: 10px; border-bottom: 1px solid #eee; background: #f5f5f5;">
        <strong>微前端调试面板</strong>
        <button onclick="this.parentElement.parentElement.style.display='none'" 
                style="float: right; background: none; border: none; cursor: pointer;">×</button>
      </div>
      <div id="debug-content" style="padding: 10px;"></div>
    `;

    document.body.appendChild(panel);
    this.updateDebugInfo();
  }

  // 更新调试信息
  updateDebugInfo() {
    if (!this.isDebugMode) return;

    const content = document.getElementById("debug-content");
    if (!content) return;

    const info = {
      currentApp: this.getCurrentApp(),
      loadedApps: this.getLoadedApps(),
      performance: this.getPerformanceInfo(),
      errors: this.getErrorInfo(),
    };

    content.innerHTML = `
      <div><strong>当前应用:</strong> ${info.currentApp || "无"}</div>
      <div><strong>已加载应用:</strong> ${
        info.loadedApps.join(", ") || "无"
      }</div>
      <div><strong>性能指标:</strong></div>
      <div style="margin-left: 10px;">
        <div>内存使用: ${info.performance.memory || "N/A"}</div>
        <div>加载时间: ${info.performance.loadTime || "N/A"}ms</div>
      </div>
      <div><strong>错误信息:</strong></div>
      <div style="margin-left: 10px;">
        <div>错误数量: ${info.errors.count}</div>
        <div>最新错误: ${info.errors.latest || "无"}</div>
      </div>
    `;
  }

  private getCurrentApp(): string | null {
    return (window as any).__CURRENT_MICRO_APP__ || null;
  }

  private getLoadedApps(): string[] {
    return (window as any).__LOADED_MICRO_APPS__ || [];
  }

  private getPerformanceInfo(): any {
    const memory = (performance as any).memory;
    return {
      memory: memory
        ? `${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB`
        : "N/A",
      loadTime: performance.timing
        ? performance.timing.loadEventEnd - performance.timing.navigationStart
        : "N/A",
    };
  }

  private getErrorInfo(): any {
    const errors = (window as any).__MICRO_APP_ERRORS__ || [];
    return {
      count: errors.length,
      latest: errors[errors.length - 1]?.message || null,
    };
  }
}

5.4 内存管理优化

5.4.1 内存泄漏检测

内存泄漏检测实现

// src/memory/MemoryLeakDetector.ts
export class MemoryLeakDetector {
  private snapshots: any[] = [];
  private maxSnapshots = 10;
  private checkInterval = 30000; // 30秒检查一次

  constructor() {
    this.startMonitoring();
  }

  private startMonitoring() {
    setInterval(() => {
      this.takeSnapshot();
      this.analyzeMemoryLeaks();
    }, this.checkInterval);
  }

  private takeSnapshot() {
    if ("memory" in performance) {
      const memory = (performance as any).memory;
      const snapshot = {
        timestamp: Date.now(),
        usedJSHeapSize: memory.usedJSHeapSize,
        totalJSHeapSize: memory.totalJSHeapSize,
        jsHeapSizeLimit: memory.jsHeapSizeLimit,
      };

      this.snapshots.push(snapshot);

      // 限制快照数量
      if (this.snapshots.length > this.maxSnapshots) {
        this.snapshots.shift();
      }
    }
  }

  private analyzeMemoryLeaks() {
    if (this.snapshots.length < 3) return;

    const recent = this.snapshots.slice(-3);
    const growth = recent[2].usedJSHeapSize - recent[0].usedJSHeapSize;
    const growthRate = growth / (recent[2].timestamp - recent[0].timestamp);

    // 如果内存增长超过阈值,报告潜在泄漏
    if (growthRate > 1000) {
      // 1KB/ms
      console.warn("Potential memory leak detected:", {
        growth,
        growthRate,
        snapshots: recent,
      });
    }
  }

  // 获取内存使用报告
  getMemoryReport() {
    if (this.snapshots.length === 0) {
      return { message: "No memory data available" };
    }

    const latest = this.snapshots[this.snapshots.length - 1];
    const first = this.snapshots[0];
    const growth = latest.usedJSHeapSize - first.usedJSHeapSize;

    return {
      current: latest.usedJSHeapSize,
      growth,
      growthRate: growth / (latest.timestamp - first.timestamp),
      snapshots: this.snapshots.length,
    };
  }
}
5.4.2 垃圾回收优化

垃圾回收优化实现

// src/memory/GarbageCollectionOptimizer.ts
export class GarbageCollectionOptimizer {
  private cleanupTasks: (() => void)[] = [];
  private isOptimizing = false;

  // 注册清理任务
  registerCleanupTask(task: () => void) {
    this.cleanupTasks.push(task);
  }

  // 执行垃圾回收优化
  async optimizeGarbageCollection() {
    if (this.isOptimizing) return;

    this.isOptimizing = true;

    try {
      // 执行所有清理任务
      for (const task of this.cleanupTasks) {
        try {
          task();
        } catch (error) {
          console.error("Cleanup task failed:", error);
        }
      }

      // 清理事件监听器
      this.cleanupEventListeners();

      // 清理定时器
      this.cleanupTimers();

      // 清理 DOM 引用
      this.cleanupDOMReferences();

      // 强制垃圾回收(如果支持)
      // 注意:gc() 不是标准的 Web API,只在特定条件下可用:
      // 1. Chrome/Edge: 需要启动时添加 --js-flags="--expose-gc" 标志
      // 2. Firefox: 需要设置 dom.allow_unsafe_from_into=true
      // 3. 生产环境中通常不可用,主要用于开发和调试
      // 4. 即使可用,也不建议在生产环境频繁调用,会影响性能
      if ("gc" in window && typeof (window as any).gc === "function") {
        (window as any).gc();
      }
    } finally {
      this.isOptimizing = false;
    }
  }

  private cleanupEventListeners() {
    // 清理未使用的事件监听器
    const elements = document.querySelectorAll("*");
    elements.forEach((element) => {
      // 这里需要根据实际情况实现事件监听器清理
      // 由于无法直接获取事件监听器,这里只是示例
    });
  }

  private cleanupTimers() {
    // 清理未使用的定时器
    // 注意:这里需要应用自己管理定时器 ID
    const timers = (window as any).__MICRO_APP_TIMERS__ || [];
    timers.forEach((timerId: number) => {
      clearTimeout(timerId);
      clearInterval(timerId);
    });
    (window as any).__MICRO_APP_TIMERS__ = [];
  }

  private cleanupDOMReferences() {
    // 清理未使用的 DOM 引用
    const elements = document.querySelectorAll("[data-micro-app]");
    elements.forEach((element) => {
      if (!element.isConnected) {
        element.remove();
      }
    });
  }
}

5.5 本章小结

本章深入探讨了微前端的高级特性与优化,包括:

  1. 沙箱机制:详细介绍了快照沙箱和代理沙箱的实现原理,以及沙箱选择策略
  2. 性能优化:通过预加载、资源缓存、懒加载等策略提升系统性能
  3. 监控与调试:实现性能监控、错误监控和调试工具,提升开发和运维效率
  4. 内存管理:通过内存泄漏检测和垃圾回收优化,确保系统稳定运行

这些高级特性是构建高性能、高可用微前端系统的关键,需要根据实际项目需求进行合理选择和配置。