首页IT科技360隔离沙箱会不会中毒(Qiankun原理详解JS沙箱是如何做隔离)

360隔离沙箱会不会中毒(Qiankun原理详解JS沙箱是如何做隔离)

时间2025-06-14 04:24:56分类IT科技浏览6377
导读:前言 相信大家也知道 qiankun 有 SnapshotSandbox, LegacySandbox 和 ProxySandbox 这些沙箱,而它们又可以分为单例和多例两种模式,网上也有很多文章对其进行介绍。...

前言

相信大家也知道 qiankun 有 SnapshotSandbox, LegacySandbox 和 ProxySandbox 这些沙箱            ,而它们又可以分为单例和多例两种模式                   ,网上也有很多文章对其进行介绍            。

但这些文章的关注点都是沙箱的环境恢复做的事      ,那 JS 的隔离到底是怎么做到的呢?

换个问法         ,当我写 window.a = 1 的时候                   ,a 是怎么被挂载到这些 XXXSandbox 上的呢?又或者我直接云修改 window.a = 123 时          ,JS 沙箱到底是怎么隔离这个 a 的呢?

总不能这样吧:

window = window.sandbox window.a = 1 // window.sandbox.a = 1

这篇文章就来简单聊聊 qiankun 沙箱那些事                   。

复习一下沙箱

这里我们还是稍微复习一下 qiankun 的三大沙箱吧      。

SanpshotSandbox

第一种是快照沙箱         。

它的原理是:把主应用的 window 对象做浅拷贝      ,将 window 的键值对存成一个 Hash Map                   。之后无论微应用对 window 做任何改动                  ,当要在恢复环境时             ,把这个 Hash Map 又应用到 window 上就可以了          。 大概如下图所示      。

稍微做下小结:

微应用 mount 时 先把上一次记录的变更 modifyPropsMap 应用到微应用的全局 window   ,没有则跳过 浅复制主应用的 window key-value 快照                  ,用于下次恢复全局环境 微应用 unmount 时 将当前微应用 window 的 key-value 和 快照 的 key-value 进行 Diff                ,Diff 出来的结果用于下次恢复微应用环境的依据 将上次快照的 key-value 拷贝到主应用的 window 上,以此恢复环境

LegacySandbox

上面的 SnapshotSandbox 有一个问题:每次微应用 unmount 时都要对每个属性值做一次 Diff               ,类似这样:

for (const prop in window) { if (window[prop] !== this.windowSnapshot[prop]) { // 记录微应用的变更 this.modifyPropsMap[prop] = window[prop]; // 恢复主应用的环境 window[prop] = this.windowSnapshot[prop]; } }

如果有 1000 个属性就要对比 1000 次                   ,不是那么优雅                  。

LegacySandbox 的想法则是 通过监听对 window 的修改来直接记录 Diff 内容   ,因为只要对 window 属性进行设置            ,那么就会有两种情况:

如果是新增属性                   ,那么存到 addedMap 里 如果是更新属性      ,那么把原来的键值存到 prevMap         ,把新的键值存到 newMap

(当然这里的变量名做了简化)

通过 addedMap, prevMap 和 newMap 这三个变量就能反推出微应用以及原来环境的变化                   ,qiankun 也能以此作为恢复环境的依据             。

当然这里的监听用到了 ES6 的新语法 Proxy          ,不过这里先不展开讨论      ,在之后的系列文章上会会自己手动实现一个简单的沙箱   。

ProxySandbox

前面两种沙箱都是 单例模式 下使用的沙箱                  。也即一个页面中只能同时展示一个微应用                  ,而且无论是 set 还是 get 依然是直接操作 window 对象                。

在这样单例模式下             ,当微应用修改全局变量时依然会在原来的 window 上做修改   ,因此如果在同一个路由页面下展示多个微应用时                  ,依然会有环境变量污染的问题。

为了避免真实的 window 被污染                ,qiankun 实现了 ProxySandbox               。它的想法是:

把当前 window 的一些原生属性(如document, location等)拷贝出来,单独放在一个对象上               ,这个对象也称为 fakeWindow 之后对每个微应用分配一个 fakeWindow 当微应用修改全局变量时: 如果是原生属性                   ,则修改全局的 window 如果是原生属性   ,则修改 fakeWindow 里的内容 微应用获取全局变量时: 如果是原生属性            ,则从 window 里拿 如果不是原生属性                   ,则优先从 fakeWindow 里获取

这样一来连恢复环境都不需要了      ,因为每个微应用都有自己一个环境         ,当在 active 时就给这个微应用分配一个 fakeWindow                   ,当 inactive 时就把这个 fakeWindow 存起来          ,以便之后再利用                   。

隔离原理

看完上面      ,你大概也知道了这些沙箱是怎么恢复环境的 但是                  ,回到我们的问题:qiankun 是怎么把 a 和这些沙箱联系起来呢?也即写下 window.a = 1 是怎么做到对 a 变量隔离的呢?

这个逻辑的实现并不在 qiankun 的源码里             ,而是在它所依赖的 import-html-entry 中   ,这里做一下简化:

const executableScript = ` ;(function(window, self, globalThis){ ;${scriptText}${sourceUrl} }).bind(window.proxy)(window.proxy, window.proxy, window.proxy); ` eval.call(window, executableScript)

把上面字符串代码展开来看看:

function fn(window, self, globalThis) { // 你的 JavaScript code } const bindedFn = fn.bind(window.proxy); bindedFn(window.proxy, window.proxy, window.proxy);

可以发现这里的代码做了三件事:

把要执行 JS 代码放在一个立即执行函数中                  ,且函数入参有 window, self, globalThis 给这个函数 绑定上下文 window.proxy 执行这个函数                ,并 把上面提到的沙箱对象 window.proxy 作为入参分别传入

因此,当我们在 JS 文件里有 window.a = 1 时               ,实际上会变成:

function fn(window, self, globalThis) { window.a = 1; } const bindedFn = fn.bind(window.proxy); bindedFn(window.proxy, window.proxy, window.proxy);

那么此时                   ,window.a 的 window 就不是全局 window 而是 fn 的入参 window 了   。又因为我们把 window.proxy 作为入参传入   ,所以 window.a 实际上为 window.proxy.a = 1            。这也正好解释了 qiankun 的 JS 隔离逻辑                   。

XXX is undefined

不知道看完上面的实现            ,你有没有发现问题      。

假如现在代码里有隐式声明或调用全局对象的代码:

add = (a, b) => { return a + b } add(1, 2)

当这样调用 add 时                   ,上下文 this 则为刚刚绑定的 window.proxy         。由于隐式声明 add 不会自动挂载到 window.proxy 上      ,所以当执行 add         ,eval 就会报 add is undefined                   。详见 这个 Issue          。

不要觉得这种情况不会发生                   ,实际上          ,这还是挺常见的:

老旧的第三方 SDK JS 文件 Webpack 插件引入的 JS 公司网关层自动注入的 JS 等等...

我之前就遇到过这种情况:比如下面 Webpack 会注入脚手架定义好的 CDN 资源重试逻辑:

<script> var __JS_RETRY__ = {}; function __rpReport(data) { console.log(__rpReport); } function __rpJsReport(loadType, msidType, url) { console.log(__rpJsReport); } function __retryPlugin(event) { console.log(retryPlugin) } // 改成下面就可以了 // window.__JS_RETRY__ = {}; // // window.__rpReport = (data) => { // console.log(__rpReport); // } // // window.__rpJsReport = (loadType, msidType, url) => { // console.log(__rpJsReport); // } // // window.__retryPlugin = (event) => { // console.log(retryPlugin) // } </script>

这个问题的解决的方法也很简单:

把代码 a = 1 改成 window.a 添加全局声明 window a

这样一来      ,你就得每次打包代码以及发布时执行一个脚本来做这些文本替换                  ,非常麻烦      。而京东的新微应用框架 MicroApp 则提供了一套插件系统:

它可以让开发者在执行 JS 前去做代码文本的替换:

import microApp from @micro-zoe/micro-app microApp.start({ plugins: { // ... modules: { appName1: [{ loader(code, url, options) { if (url === xxx.js) { // 替换有问题的代码 code = code.replace(var abc =, window.abc =) } return code } }], } } })

如果要对接别的团队的微应用时             ,而且正好他们有 a = 1 这样的代码   ,那么在加载微应用的时候直接修复全局变量的问题                  ,不需要通知他们修改                ,也不失为一种策略吧                  。

总结

总结一下,qiankun 一共有 3 种沙箱:

SnapshotSandbox:记录 window 对象               ,每次 unmount 都要和微应用的环境进行 Diff LegacySandbox:在微应用修改 window.xxx 时直接记录 Diff                   ,将其用于环境恢复 ProxySandbox:为每个微应用分配一个 fakeWindow   ,当微应用操作 window 时            ,其实是在 fakeWindow 上操作

要和这些沙箱结合起来使用                   ,qiankun 会把要执行的 JS 包裹在立即执行函数中      ,通过绑定上下文和传参的方式来改变 this 和 window 的值         ,让它们指向 window.proxy 沙箱对象                   ,最后再用 eval 来执行这个函数             。

以上就是Qiankun原理详解JS沙箱是如何做隔离的详细内容          ,更多关于Qiankun原理JS沙箱隔离的资料请关注本站其它相关文章!

声明:本站所有文章      ,如无特殊说明或标注                  ,均为本站原创发布   。任何个人或组织             ,在未征得本站同意时   ,禁止复制             、盗用                  、采集      、发布本站内容到任何网站          、书籍等各类媒体平台                  。如若本站内容侵犯了原著者的合法权益                  ,可联系我们进行处理                。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
网站排名是以( )的形式展现在搜索结果中(网站排名的指标有哪些?) promise对象的用法(Promise对象的使用)