全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-09-28_Qiankun原理——JS沙箱是怎么做隔离的

您的位置:首页 >> 新闻 >> 行业资讯

Qiankun原理——JS沙箱是怎么做隔离的 本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 前言哈喽,大家好,我是海怪。 相信大家也知道 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上就可以了。大概如下图所示。 SnapshotSandbox稍微做下小结: 微应用 mount 时先把上一次记录的变更modifyPropsMap应用到微应用的全局window,没有则跳过浅复制主应用的windowkey-value 快照,用于下次恢复全局环境微应用 unmount 时将当前微应用window的key-value和快照的key-value进行 Diff,Diff 出来的结果用于下次恢复微应用环境的依据将上次快照的key-value拷贝到主应用的window上,以此恢复环境LegacySandbox上面的SnapshotSandbox有一个问题:每次微应用 unmount 时都要对每个属性值做一次 Diff,类似这样: for(constpropinwindow){ 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中,这里做一下简化: constexecutableScript=` (function(window,self,globalThis){ ${scriptText}${sourceUrl} }).bind(window.proxy)(window.proxy,window.proxy,window.proxy); ` eval.call(window,executableScript) 把上面字符串代码展开来看看: functionfn(window,self,globalThis){ //你的JavaScriptcode } constbindedFn=fn.bind(window.proxy); bindedFn(window.proxy,window.proxy,window.proxy); 可以发现这里的代码做了三件事: 把要执行 JS 代码放在一个立即执行函数中,且函数入参有window,self,globalThis给这个函数绑定上下文window.proxy执行这个函数,并把上面提到的沙箱对象window.proxy作为入参分别传入因此,当我们在 JS 文件里有window.a = 1时,实际上会变成: functionfn(window,self,globalThis){ window.a=1; } constbindedFn=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)={ returna+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 前去做代码文本的替换: importmicroAppfrom'@micro-zoe/micro-app' microApp.start({ plugins:{ //... modules:{ 'appName1':[{ loader(code,url,options){ if(url==='xxx.js'){ //替换有问题的代码 code=code.replace('varabc=','window.abc=') } returncode } }], } } }) 如果要对接别的团队的微应用时,而且正好他们有a = 1这样的代码,那么在加载微应用的时候直接修复全局变量的问题,不需要通知他们修改,也不失为一种策略吧。 总结总结一下,qiankun 一共有 3 种沙箱: SnapshotSandbox:记录window对象,每次 unmount 都要和微应用的环境进行 DiffLegacySandbox:在微应用修改window.xxx时直接记录 Diff,将其用于环境恢复ProxySandbox:为每个微应用分配一个fakeWindow,当微应用操作window时,其实是在fakeWindow上操作要和这些沙箱结合起来使用,qiankun 会把要执行的 JS 包裹在立即执行函数中,通过绑定上下文和传参的方式来改变this和window的值,让它们指向window.proxy沙箱对象,最后再用eval来执行这个函数。 阅读原文

上一篇:2022-11-06_111页,张益唐攻克朗道-西格尔零点猜想论文公布,8号B站直播开讲 下一篇:2024-02-02_大模型也有小偷?为保护你的参数,上交大给大模型制作「人类可读指纹」

TAG标签:

15
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价