New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
从零开始写一个微前端框架-沙箱篇 #19
Labels
docs
Improvements or additions to documentation
Comments
This was referenced Aug 4, 2021
Closed
Closed
Closed
很强 |
m |
amazing |
和乾坤的js sandbox有什么区别? |
对于沙箱的实现上,用了 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
前言
自从微前端框架micro-app开源后,很多小伙伴都非常感兴趣,问我是如何实现的,但这并不是几句话可以说明白的。为了讲清楚其中的原理,我会从零开始实现一个简易的微前端框架,它的核心功能包括:渲染、JS沙箱、样式隔离、数据通信。由于内容太多,会根据功能分成四篇文章进行讲解,这是系列文章的第二篇:沙箱篇。
通过这些文章,你可以了解微前端框架的具体原理和实现方式,这在你以后使用微前端或者自己写一套微前端框架时会有很大的帮助。如果这篇文章对你有帮助,欢迎点赞留言。
相关推荐
开始
前一篇文章中,我们已经完成了微前端的渲染工作,虽然页面已经正常渲染,但是此时基座应用和子应用是在同一个window下执行的,这有可能产生一些问题,如全局变量冲突、全局事件监听和解绑。
下面我们列出了两个具体的问题,然后通过创建沙箱来解决。
问题示例
1、子应用向window上添加一个全局变量:
globalStr='child'
,如果此时基座应用也有一个相同的全局变量:globalStr='parent'
,此时就产生了变量冲突,基座应用的变量会被覆盖。2、子应用渲染后通过监听
scroll
添加了一个全局监听事件当子应用被卸载时,监听函数却没有解除绑定,对页面滚动的监听一直存在。如果子应用二次渲染,监听函数会绑定两次,这显然是错误的。
接下来我们就通过给微前端创建一个JS沙箱环境,隔离基座应用和子应用的JS,从而解决这两个典型的问题,
创建沙箱
由于每个子应用都需要一个独立的沙箱,所以我们通过class创建一个类:SandBox,当一个新的子应用被创建时,就创建一个新的沙箱与其绑定。
我们使用Proxy进行代理操作,代理对象为空对象
microWindow
,得益于Proxy强大的功能,实现沙箱变得简单且高效。在
constructor
中进行代理相关操作,通过Proxy代理microWindow
,设置get
、set
、deleteProperty
三个拦截器,此时子应用对window的操作基本上可以覆盖。创建完代理后,我们接着完善
start
和stop
两个方法,实现方式也非常简单,具体如下:上面一个沙箱的雏形就完成了,我们尝试一下,看看是否有效。
使用沙箱
在
src/app.js
中引入沙箱,在CreateApp
的构造函数中创建沙箱实例,并在mount
方法中执行沙箱的start方法,在unmount
方法中执行沙箱的stop方法。我们在上面创建了沙箱实例并启动沙箱,这样沙箱就生效了吗?
显然是不行的,我们还需要将子应用的js通过一个with函数包裹,修改js作用域,将子应用的window指向代理的对象。形式如:
在sandbox中添加方法
bindScope
,修改js作用域:然后在mount方法中添加对
bindScope
的使用到此沙箱才真正起作用,我们验证一下问题示例中的第一个问题。
先关闭沙箱,由于子应用覆盖了基座应用的全局变量
globalStr
,当我们在基座中访问这个变量时,得到的值为:child
,说明变量产生了冲突。开启沙箱后,重新在基座应用中打印
globalStr
的值,得到的值为:parent
,说明变量冲突的问题已经解决,沙箱正确运行。第一个问题已经解决,我们开始解决第二个问题:全局监听事件。
重写全局事件
再来回顾一下第二个问题,错误的原因是在子应用卸载时没有清空事件监听,如果子应用知道自己将要被卸载,主动清空事件监听,这个问题可以避免,但这是理想情况,一是子应用不知道自己何时被卸载,二是很多第三方库也有一些全局的监听事件,子应用无法全部控制。所以我们需要在子应用卸载时,自动将子应用残余的全局监听事件进行清空。
我们在沙箱中重写
window.addEventListener
和window.removeEventListener
,记录所有全局监听事件,在应用卸载时如果有残余的全局监听事件则进行清空。创建一个
effect
函数,在这里执行具体的操作在沙箱的构造函数中执行effect方法,得到卸载的钩子函数releaseEffect,在沙箱关闭时执行卸载操作,也就是在stop方法中执行releaseEffect函数
这样重写全局事件及卸载的操作基本完成,我们验证一下是否正常运行。
首先关闭沙箱,验证问题二的存在:卸载子应用后滚动页面,依然在打印scroll,说明事件没有被卸载。
开启沙箱后,卸载子应用,滚动页面,此时scroll不再打印,说明事件已经被卸载。
从截图中可以看出,除了我们主动监听的
scroll
事件,还有error
、unhandledrejection
等其它全局事件,这些事件都是由框架、构建工具等第三方绑定的,如果不进行清空,会导致内存无法回收,造成内存泄漏。沙箱功能到此就基本完成了,两个问题都已经解决。当然沙箱需要解决的问题远不止这些,但基本架构思路是不变的。
结语
JS沙箱的核心在于修改js作用域和重写window,它的使用场景不限于微前端,也可以用于其它地方,比如在我们向外部提供组件或引入第三方组件时都可以使用沙箱来避免冲突。
下一篇文章我们会完成微前端的样式隔离。
The text was updated successfully, but these errors were encountered: