软件教程 2025年08月6日
0 收藏 0 点赞 934 浏览 5751 个字
摘要 :

文章目录 一、为什么需要JS沙箱 二、SnapshotSandbox沙箱 (一)SnapshotSandbox沙箱的原理 (二)SnapshotSandbox沙箱的代码实现 三、ProxySandbox沙箱 (一)Proxy……




  • 一、为什么需要JS沙箱
  • 二、SnapshotSandbox沙箱
    • (一)SnapshotSandbox沙箱的原理
    • (二)SnapshotSandbox沙箱的代码实现
  • 三、ProxySandbox沙箱
    • (一)ProxySandbox沙箱的原理
    • (二)ProxySandbox沙箱的简易实现示例
    • (三)解决兼容性问题与实现真正的隔离
    • (四)qiankun中ProxySandbox的具体实现
  • 四、关于qiankun沙箱的常见问题解答
    • (一)import-html-entry中的子应用信息缓存会造成内存泄漏吗?
    • (二)如果研发团队代码都很标准,是否可以不开启沙箱?

    乾坤(qiankun)作为一款热门的微前端框架,其中,沙箱隔离机制是保障微应用独立运行的关键,尤其是JS沙箱,在防止微应用之间的代码冲突方面发挥着重要作用。接下来,我们就深入探究一下qiankun中JS沙箱的原理与实现。

    一、为什么需要JS沙箱

    在实际开发中,我们经常会遇到一些棘手的问题。比如,我之前参与一个JSP项目时,就发现JSON.stringify({name: \'张三\'})的结果是\'\"{\\\\\"name\\\\\":\\\\\"张三\\\\\"}\"\',而当使用JSON.parse()解析时,得到的竟然还是字符串。经过仔细审查,才发现原来是JSON方法被重写了。

    这种情况并非个例,有些插件为了实现特定功能,会重写很多方法。像Vue2为了实现数据监听,重写了Array的一系列方法;single - spa为了监听路由,重写了pushStatereplaceState等方法(不过这些重写一般不会影响原API的正常使用)。但像我遇到的JSON方法被重写的情况,就直接影响了原API的执行结果。

    qiankun中默认开启了js、window沙箱,这是非常有必要的。它主要通过三种沙箱来实现隔离:SnapshotSandboxProxySandbox以及下文会提到的LegacySandbox(原文未详细提及,简单介绍下)。接下来,我们分别深入了解一下这几种沙箱的实现原理。

    二、SnapshotSandbox沙箱

    (一)SnapshotSandbox沙箱的原理

    SnapshotSandbox是针对不支持proxy api的低版本浏览器设计的,它采用快照的形式来实现沙箱隔离。简单来说,就是在微应用加载时,记录下当前window对象的状态(也就是拍个“快照”),当微应用离开时,对比当前window对象和快照的差异,把更改的部分恢复,并记录这些变更;再次进入微应用时,恢复之前记录的变更。

    (二)SnapshotSandbox沙箱的代码实现

    下面我们结合代码来深入理解一下:

    import type { SandBox } from \'../interfaces\';
    import { SandBoxType } from \'../interfaces\';
    
    // 定义一个函数,用于循环遍历window对象的属性,并对每个属性执行回调函数
    function iter(obj: typeof window, callbackFn: (prop: any) => void) { 
      // 使用for...in循环遍历对象的属性
      // eslint-disable-next-line guard-for-in, no-restricted-syntax
      for (const prop in obj) { 
        // 处理兼容性问题,对于clearInterval属性特殊处理
        if (obj.hasOwnProperty(prop) || prop === \'clearInterval\') { 
          callbackFn(prop);
        }
      }
    }
    
    /**
     * 基于 diff 方式实现的沙箱,用于不支持 Proxy 的低版本浏览器
     */
    export default class SnapshotSandbox implements SandBox {
      proxy: WindowProxy;
      name: string;
      type: SandBoxType;
      sandboxRunning = true;
      private windowSnapshot!: Window;
      private modifyPropsMap: Record<any, any> = {};
    
      constructor(name: string) {
        this.name = name;
        this.proxy = window;
        this.type = SandBoxType.Snapshot;
      }
    
      // 当微应用激活(初次进入该微应用)时执行
      active() { 
        // 记录当前window的快照
        this.windowSnapshot = {} as Window; 
        iter(window, (prop) => {
          this.windowSnapshot[prop] = window[prop];
        });
    
        // 恢复之前的变更
        Object.keys(this.modifyPropsMap).forEach((p: any) => {
          window[p] = this.modifyPropsMap[p]; 
        });
    
        this.sandboxRunning = true;
      }
    
      // 当微应用失活(离开微应用)时执行
      inactive() { 
        this.modifyPropsMap = {};
    
        iter(window, (prop) => { 
          if (window[prop]!== this.windowSnapshot[prop]) {
            // 记录变更,恢复环境
            this.modifyPropsMap[prop] = window[prop]; 
            window[prop] = this.windowSnapshot[prop]; 
          }
        });
    
        if (process.env.NODE_ENV === \'development\') {
          console.info(`[qiankun:sandbox] ${this.name} origin window restore...`, Object.keys(this.modifyPropsMap));
        }
    
        this.sandboxRunning = false;
      }
    }
    

    在这段代码中:

    • active方法在微应用首次加载时被调用,它先创建一个空的windowSnapshot对象,然后通过iter函数遍历window对象,将每个属性的值复制到windowSnapshot中,完成当前window状态的记录。接着,恢复之前离开微应用时记录的变更。
    • inactive方法在离开微应用时执行,它先清空modifyPropsMap,然后再次遍历window对象,对比当前属性值和windowSnapshot中的值。如果有差异,就将当前值记录到modifyPropsMap中,并把window对象的属性值恢复为快照中的值。深入讲解qiankun的JS沙箱隔离机制原理与实践

    需要注意的是,快照沙箱只是进行了一层浅拷贝对比。例如,对console对象内部方法的更改可能不会被记录下来,这种情况可能会影响其他子应用。如果有更复杂的需求,开发者可能需要自行记录原本的变更。

    三、ProxySandbox沙箱

    (一)ProxySandbox沙箱的原理

    ProxySandbox是针对支持proxy的浏览器实现的沙箱机制。它的核心原理是利用JavaScript的Proxy对象,创建一个代理对象来拦截对window对象的访问,从而实现对微应用环境的隔离。

    (二)ProxySandbox沙箱的简易实现示例

    为了更好地理解,我们先来看一个简易版本的实现示例:

    const proxy = {}
    (function(window) {
      // code 部分
        console.log(window,window.console, console) // {}, undefined, console {debug: ƒ, error: ƒ, info: ƒ, log: ƒ, warn: ƒ, …}
    }(proxy))
    

    在这个示例中,我们创建了一个空对象proxy,并将其作为参数传递给一个立即执行函数。在函数内部,打印windowwindow.consoleconsole。可以看到,window是一个空对象,window.consoleundefined,而console是正常的console对象。这表明,通过这种方式,我们可以在一定程度上模拟一个独立的window环境。

    (三)解决兼容性问题与实现真正的隔离

    然而,上述示例还存在一些问题,比如如何兼容console的API呢?这就用到了with语句。with语句可以扩展一个语句的作用域链,允许在代码块中直接使用对象的属性和方法,而无需重复引用对象。但需要注意的是,with语句在严格模式下是被禁止使用的,因为它可能会使代码难以理解和维护。

    在实际的ProxySandbox实现中,我们可以这样使用with语句:

    const proxy = new Proxy(window, {})
    (function(window) {
      with (proxy) {
         // code 部分
        console.log(window,window.console, console) // {}, undefined, console {debug: ƒ, error: ƒ, info: ƒ, log: ƒ, warn: ƒ, …}
      }
    }(proxy))
    

    这样,就解决了使用window全局API并且走代理的问题。但这种简单的代理方式也会污染原本的window对象(被浅拷贝了)。例如:

    a = {name: 1, age: 2} {name:1, age:2} 
    b = new Proxy(a, {}) < Proxy (0bject) {name: 1, age: 2} 
    b.six =3 <3 
    a <{name:1, age: 2, six: 3} 
    

    可以看到,对代理对象b添加属性,会影响到原对象a

    (四)qiankun中ProxySandbox的具体实现

    那么,乾坤中的ProxySandbox是如何实现的呢?深入讲解qiankun的JS沙箱隔离机制原理与实践我们来看一下createFakeWindowAPI的代码:

    const rawObjectDefineProperty = Object.defineProperty;
    
    function createFakeWindow(globalContext: Window) {
      const propertiesWithGetter = new Map<PropertyKey, boolean>();
      const fakeWindow = {} as FakeWindow;
    
      Object.getOwnPropertyNames(globalContext)
       .filter((p) => {
          const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
          return!descriptor?.configurable;
        }) 
       .forEach((p) => {
          const descriptor = Object.getOwnPropertyDescriptor(globalContext, p); 
          if (descriptor) {
            const hasGetter = Object.prototype.hasOwnProperty.call(descriptor, \'get\');
            if (
              p === \'top\' ||
              p === \'parent\' ||
              p ===\'self\' ||
              p === \'window\' ||
              (process.env.NODE_ENV === \'test\' && (p ===\'mockTop\' || p ===\'mockSafariTop\'))
            ) {
              descriptor.configurable = true;
              if (!hasGetter) {
                descriptor.writable = true;
              }
            }
            if (hasGetter) propertiesWithGetter.set(p, true);
            rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor)); 
          }
        });
    
      return {
        fakeWindow,
        propertiesWithGetter,
      };
    }
    

    在这段代码中:

    • 首先,通过Object.getOwnPropertyNames获取globalContext(也就是window对象)的所有属性名,并过滤掉那些可配置的属性。
    • 然后,对剩下的属性获取其描述符,并根据属性名进行一些特殊处理,比如将topparentselfwindow等属性设置为可配置的。
    • 最后,使用Object.defineProperty将这些属性定义到fakeWindow对象中,并冻结它们,防止意外修改。

    通过这种方式,创建了一个相对独立的fakeWindow对象,实现了一定程度的环境隔离。深入讲解qiankun的JS沙箱隔离机制原理与实践在实际应用中,如果在乾坤的一个子应用环境下更改了window原本全局的属性,在子应用中这个属性也会被更改。深入讲解qiankun的JS沙箱隔离机制原理与实践这是因为在proxyget操作中,如果fakeWindow没有该API,还是会从全局的window上获取。至于为什么没有对window上的API进行深拷贝做绝对隔离,大概率是出于性能方面的考虑。

    四、关于qiankun沙箱的常见问题解答

    (一)import-html-entry中的子应用信息缓存会造成内存泄漏吗?

    const styleCache = {};
    const scriptCache = {};
    const embedHTMLCache = {};
    

    正常情况下,这种缓存不会造成内存泄漏。因为它是随着加载过的子应用数量增长的,并非持续无限制地增长。除非在特殊场景下,比如子应用数量成百上千,并且都在同一个tab页面执行过,才可能出现问题。在一般情况下,开发者可以放心使用。

    (二)如果研发团队代码都很标准,是否可以不开启沙箱?

    如果团队代码编写非常规范,且不在window对象上放置全局方法,同时注意bodyhtml:root等相关元素的使用,那么是可以不开启沙箱的。实际上,在正常项目中,也很少会在全局window上放置东西,所以在满足上述条件时,不开启沙箱也是可行的。

    通过对qiankun中JS沙箱的深入分析,我们了解了其原理、实现方式以及在实际应用中的一些注意事项。希望这些内容能帮助大家在使用qiankun进行微前端开发时,更好地理解和运用沙箱隔离机制。

微信扫一扫

支付宝扫一扫

版权: 转载请注明出处:https://www.zuozi.net/6966.html

管理员

相关推荐
2025-08-06

文章目录 一、Promise基础回顾 二、Promise 与 axios 结合使用场景及方法 (一)直接返回 axios …

269
2025-08-06

文章目录 一、模块初始化时的内部机制 二、常见导出写法的差异分析 (一)写法一:module.exports…

107
2025-08-06

文章目录 一、ResizeObserver详解 (一)ResizeObserver是什么 (二)ResizeObserver的基本用法 …

683
2025-08-06

文章目录 一、前期准备工作 (一)下载相关文件 (二)安装必要工具 二、处理扣子空间生成的文件…

338
2025-08-06

文章目录 一、官方文档 二、自动解包的数据类型 ref对象:无需.value即可访问 reactive对象:保持…

371
2025-08-06

文章目录 一、Hooks的工作原理 二、在if语句中使用Hook会出什么岔子? 三、React官方的Hook使用规…

843
发表评论
暂无评论

还没有评论呢,快来抢沙发~

助力内容变现

将您的收入提升到一个新的水平

点击联系客服

在线时间:08:00-23:00

客服QQ

122325244

客服电话

400-888-8888

客服邮箱

122325244@qq.com

扫描二维码

关注微信客服号