嗨喽,大家好,我是前端明明,最近这几天在学习乾坤微前端在这里分享下乾坤做js隔离的三种方式。它们分别是SnapshotSandbox,LegacySandbox ,ProxySandbox 等沙箱。
什么是沙箱
沙箱,即sandbox,顾名思义,就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。
JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
1. 为啥要有js沙箱
假如说我们在子应用A上的window 定义了一个变量window.a =3, 在子应用B上又定义了一个变量 window.b =4 , 当我们A ,B应用切换时那么window.a 的值怎么处理。正确的肯定是在A应用上window.a=3 ,切到b 应用上window.b = 4 。实现这个就需要用到沙箱。实现沙箱乾坤用了以下三种方式,各有优略点。
2. SnapshotSandbox 快照沙箱
顾名思义,快照沙箱就是,给你拍一张照片,记录你此时的状态,乾坤的快照沙箱是根据diff 来实现的,主要用于不支持window.Proxy的低版本浏览器,而且只是使用雨单个的自用用
快照沙箱的原理
激活沙箱时,将window的快照信息存在windowSnapshot中,如果modifyPropsMap有值,还需要还原上次的状态,激活期间,可能修改了window的数据,退出沙箱,将修改过的信息存到modifyPropsMap 里面,并且吧window ,还原初始进入的状态
class Sanapshotbox {
// 激活
windowSnapshot = {}
modifyPropsMap = {}
active() {
// 保存window 对象上所有属性的状态
for (const prop in window) {
this.windowSnapshot[prop] = window[prop]
}
// 恢复上一次在运行微应用的时候改过的window 上的属性
Object.keys(this.modifyPropsMap).forEach(prop => {
window[prop] = this.modifyPropsMap[prop]
})
}
// 失活
inactive() {
// 记录修改window 上的哪些属性
for (const prop in window) {
if (window[prop] !== this.windowSnapshot[prop]) {
this.modifyPropsMap[prop] = window[prop]
// 将window 上的属性状态 还原至微应用之前的状态
window[prop] = this.windowSnapshot[prop]
}
}
}
}
window.city="beijing" // 初始值
let sanapshotbox = new Sanapshotbox()
console.log("111",window.city)
sanapshotbox.active() // 微应用运行
window.city="上海"
console.log("22222",window.city)
sanapshotbox.inactive() // 微应用卸载了
console.log('3333',window.city)
缺点
- 需要遍历window上的所有属性,性能差
- 同时间内只能激活一个微应用
代理沙箱
Qiankun 基于es6 的Proxy 实现了两种应用场景不同的沙箱,一种是legacySandbox(单例),一种是proxySandbox(多例)。因为都是基于Proxy实现的,所以称为代理沙箱。
legacySandbox实现原理
legacySandbox 设置三个参数来记录全局变量,分别记录沙箱新增的全局变量addedPropsMapInSandbox,记录沙箱更新的全局变量modifiedPropsOriginalValueMapInSandbox,用于在任意时刻做snapshot的currentUpdatePropsValueMap。
legacySandbox demo
class LegacySandbox {
// 持续记录新增和修改的全局变量
currentUpdatePropsValueMap = new Map()
// 沙箱期间更新的全局变量
modifiedPropsOriginalValueMapInSandbox = new Map()
// 沙箱期间新增的全局变量
addedPropsMapInSandbox = new Map()
propsWindow = {}
// 核心逻辑
constructor() {
const fakeWindow = Object.create(null)
// 设置值或者获取值
this.propsWindow = new Proxy(fakeWindow, {
set: (target, prop, value, receiver) => {
const originValue = window[prop]
if (!window.hasOwnProperty(prop)) {
this.addedPropsMapInSandbox.set(prop, value)
} else if(!this.modifiedPropsOriginalValueMapInSandbox.has(prop)){
this.modifiedPropsOriginalValueMapInSandbox.set(prop, originValue)
}
this.currentUpdatePropsValueMap.set(prop,value)
window[prop]= value
},
get: (target, prop, receiver) => {
return window[prop]
}
})
}
setWindowProp(prop, value, isToDelete) {
if (value === undefined && isToDelete) {
delete window[prop]
} else {
window[prop] = value
}
}
active() {
// 恢复上一次该微应用处于运行状态时,对window 上做的所有应用的修改
this.currentUpdatePropsValueMap.forEach((value, prop) => {
this.setWindowProp(prop, value)
})
}
// 失活
inactive() {
// 还原window上的属性
this.modifiedPropsOriginalValueMapInSandbox.forEach((value, prop) => {
this.setWindowProp(prop, value)
})
// 删除在微应用运行期间 window 新增的属性
this.addedPropsMapInSandbox.forEach((_, prop) => {
this.setWindowProp(prop, undefined, true)
})
}
}
window.city="beijing"
let LegacySandbox01 = new LegacySandbox()
console.log('11111',window.city)
LegacySandbox01.active()
LegacySandbox01.propsWindow.city ="shanghai"
console.log('2222',window.city)
LegacySandbox01.inactive()
console.log('3333',window.city)
proxySandbox(多例沙箱)
多例沙箱原理
激活沙箱后,每次对window 取值的时候,先从自己沙箱环境的fakeWindow 里面找,如果不存在,就从
rawWindow
(外部的window
)里去找;当对沙箱内部window 对象赋值的时候,会直接操作fakeWindow,而不会影响到rawWindow
class ProxySandbox {
proxyWindow
isRunning = false
active() {
this.isRunning = true
}
inactive() {
this.isRunning = false
}
constructor() {
const fakeWindow = Object.create(null)
this.proxyWindow = new Proxy(fakeWindow, {
set: (target, prop, value, receiver) => {
if(this.isRunning){
target[prop] = value
}
},
get: (target, prop, receiver) => {
return prop in target ?target[prop]:window[prop];
}
})
}
}
window.city="beijing"
let proxySandbox01 = new ProxySandbox()
let proxySandbox02 = new ProxySandbox()
proxySandbox01.active()
proxySandbox02.active()
proxySandbox01.proxyWindow.city = 'shanghai'
proxySandbox02.proxyWindow.city = 'chengdu'
console.log("111111",window.city)
console.log("222222",proxySandbox01.proxyWindow.city)
console.log("333333",proxySandbox01.proxyWindow.city)
proxySandbox01.inactive()
proxySandbox02.inactive()
console.log("111111",window.city)
console.log("222222",proxySandbox01.proxyWindow.city)
console.log("333333",proxySandbox01.proxyWindow.city)