乾坤
1.乾坤的 3 种 JS 隔离机制
- 支持单个微应用的方案一:SnapshotSandBox
- 遍历了 window,性能不好,同一时间只能激活一个微应用
- 支持单个微应用的方案二:LegacySandBox
- 优势:不需要遍历 window 上的所有属性
- 劣势:同一时间只能激活一个微应用
- 支持同时激活多个微应用的方案:ProxySandBox
- 不需要遍历 window 上所有属性,性能良好,同一时间可以激活多个 微应用
LegacySandBox 其实也是代理方式,由于之后有了更好的方案 ProxySandBox ,支持多个微应用,因此会被逐步淘汰, 而 SnapshotSandBox 虽然遍历 window,性能不是很好,但是由于兼容性很好,因此还会存在很长的时间
SnapshotSandBox
/**
* SnapshotSandBox
* 快照沙箱,主要用于不支持proxy的浏览器(低版本浏览器)
* 激活沙箱时候:将window信息存储到windowSnapshot中,如果modifyPropMap中有值,还需要
* 还原上次的状态;激活期间,可能修改了window的数据,退出沙箱时将修改过的信息存储到modifyPropMap中
* 并且把window还原
*
* new SnapshotSandbox() => 将window的快照信息存储到windowSnapshot中,并且从modifyPropMap中还原上次修改的值 => 退出前:对windowSnapshot 和 当前window做diff比较,对于变更的信息存到modifyPropMap中,同时将windowSnapshot还原给window(还原进入时候的window)
*/
class SnapshotSandbox {
windowSnapshot = {}
modifyPropMap = {}
// 微应用处于运行的状态
active() {
// 1.保存window对象上所有属性的状态
for (const prop in window) {
this.windowSnapshot[prop] = window[prop]
}
// 2.恢复上一次在运行该微应用的时候所修改过的window上的属性
Object.keys(this.modifyPropMap).forEach(prop => {
window[prop] = this.modifyPropMap[prop]
})
}
// 微应用即将退出状态
inactive() {
for (const prop in window) {
if (window[prop] !== this.windowSnapshot[prop]) {
// 1.记录修改了window上的哪些属性
this.modifyPropMap[prop] = window[prop]
// 2.将window上的属性状态还原至微应用运行之前的状态
window[prop] = this.windowSnapshot[prop]
}
}
}
}
window.city = 'Beijing'
const snapshotSandbox = new SnapshotSandbox()
console.log(window.city + '--------01')
snapshotSandbox.active()
window.city = 'Shanghai'
console.log(window.city + '--------02')
snapshotSandbox.inactive()
console.log(window.city + '--------03')
snapshotSandbox.active()
console.log(window.city + '--------04')
LegacySandBox
// LegacySandBox
// 单例代理盒子
class LegacySandBox {
currentUpdateValueMap = new Map()
modifedPropsOriginalValueMapSandBox = new Map()
addedPropsMapInSandBox = new Map()
proxyWindow = {}
constructor() {
const fakeWindow = Object.create(null)
this.proxyWindow = new Proxy(fakeWindow, {
set: (target, prop, value, receiver) => {
const originalVal = window[prop]
if (!window.hasOwnProperty(prop)) {
this.addedPropsMapInSandBox.set(prop, value)
} else if (!this.modifedPropsOriginalValueMapSandBox.has(prop)) {
this.modifedPropsOriginalValueMapSandBox.set(prop, originalVal)
}
this.currentUpdateValueMap.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.currentUpdateValueMap.forEach((value, prop) => {
this.setWindowProp(prop, value)
})
}
inactive() {
// 还原window上原有的属性
this.modifedPropsOriginalValueMapSandBox.forEach((value, prop) => {
this.setWindowProp(prop, value)
})
// 删除在微应用运行期间window新增的属性
this.addedPropsMapInSandBox.forEach((_, prop) => {
this.setWindowProp(prop, undefined, true)
})
}
}
window.city = 'Beijing'
const legacySandBox = new LegacySandBox()
console.log(window.city + '--------01')
legacySandBox.active()
legacySandBox.proxyWindow.city = 'Shanghai'
console.log(window.city + '--------02')
legacySandBox.inactive()
console.log(window.city + '--------03')
ProxySandBox
// ProxySandBox
// 多例代理盒子
// 创建一个代理对象,当获取的属性并不在设置的空对象fakeWindow上则直接去window属性上找
// 而设置的时候则判断该微应用是否激活,如果激活则直接改fakeWindow,没有激活,直接不管该设置操作
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 proxySandBox1 = new ProxySandBox()
let proxySandBox2 = new ProxySandBox()
proxySandBox1.active()
proxySandBox2.active()
proxySandBox1.proxyWindow.city = 'Shanghai'
proxySandBox2.proxyWindow.city = 'Chengdu'
console.log('proxySandBox1----', proxySandBox1.proxyWindow.city);
console.log('proxySandBox2----', proxySandBox2.proxyWindow.city);
console.log('window.city----', window.city);
console.log('⬇️⬇️⬇️⬇️⬇️');
proxySandBox1.inactive()
proxySandBox2.inactive()
console.log('proxySandBox1----', proxySandBox1.proxyWindow.city);
console.log('proxySandBox2----', proxySandBox2.proxyWindow.city);
console.log('window.city-----', window.city);