一. 如何应用沙箱
所有沙箱都是一个类,且都有 proxy 属性, inactive,active 两个方法,分别控制沙箱的打开和关闭。在qiankun中,每一个子应用都会有独属于自己的沙箱实例:
- active:启动当前沙箱环境
- inactive:关闭当前沙箱环境
- proxy属性:代入 execScript 的 proxy,即子应用的window属性是谁。
沙箱的使用可以理解就是一个IIFE,类似于webpack的运行时对模块化的实现,在乾坤中,它实际上也是 execScript 的原理:
eval(
'(function(window) {
// ... 子应用脚本
})(proxy)' // 代入的proxy,就是对应沙箱实例的proxy属性
)
二. 快照沙箱(SnapshotSandbox)
应用特点:
- 不支持 proxy 时选用的沙箱,只支持单例,即只能同时存在一个子应用。
- 子应用上下文直接对window进行修改。
实现思路:
- active时,去给window拍照。遍历window上的所有自有属性,this.windowSnapshot[key] = window[key]
- 遍历this.modifyPropsMap,对window所有属性进行子应用定制化修改,window[key] = this.modifyPropsMap[key]
- 子应用的js逻辑修改的就是货真价实的window,在inactive时,向this.modifyPropsMap登记变化的属性,随后将window还原
所存在的问题:
- 这样实现没有深度clone,如果有修改window.obj = {} 中的某些属性,inactive时候会认为没有发生改变,我认为应该采取深层clone,并递归对比。
class SnapshotSandbox {
constructor () {
this.windowSnapshot = {}
this.modifyPropsMap = {}
}
active() {
// 1. 给window拍照
Object.keys(window).forEach(key => {
this.windowSnapshot[key] = window[key]
})
// 2. 把window还原至沙箱需要的状态
Object.keys(this.modifyPropsMap).forEach(key => {
window[key] = this.modifyPropsMap[key]
})
}
inactive() {
// 1. 遍历当前window,根据windowSnapshot还原window
Object.keys(window).forEach(key => {
if (this.windowSnapshot[key] !== window[key]) {
// 如果当前window[key]和this.windowSnapshot[key]不相等,说明key修改过
this.modifyPropsMap[key] = window[key] // 更新key至modify
this.window[key] = this.windowSnapshot[key] // 将照片属性还原至window
}
})
}
}
三. Proxy单例沙箱(LegacySandBox)
应用特点:
- 支持proxy,且只支持单例,即同时渲染一个子应用
- 子应用上下文,给window一个proxy,通过proxy修改window,子应用运行期间会切实修改window
实现思路:
-
constructor创建proxy,
- set:增加的属性收录在 addedPropsMapInSandbox 中,修改的属性收录在 modifiedPropsOriginalValueMapInSandbox,无论增加还是修改,都收录在 currentUpdatedPropsValueMap 中。然后切实修改window对应属性。
- get:直接从window中取值
-
active:遍历 currentUpdatePropsValueMap ,然后将对应的k-v设置到window上。
-
inactive:
- 遍历 addedPropsMapInSandbox,删除 window上对应的k-v
- 遍历 modifiedPropsOriginalValueMapInSandbox,将window还原至对应k-v
class LegacySandbox {
constructor () {
this.addPropsMap = new Map([])
this.modifiedProps = new Map([])
this.updatedProps = new Map([]) // 感觉没啥必要,addPropsMap和modifiedProps的总和不就是updatedProps么
const fakeWindow = Object.create(null)
this.proxy = new Proxy(fakeWindow, {
set(key, value) {
// 1. 增加的属性,添加到 addPropsMap
if (!window.hasOwnProperty(key)) {
this.addPropsMap.set(key, value)
} else if (!this.modifiedProps.has(key)) {
// 2. 修改的属性,记录在 modifiedProps
this.modifiedProps.set(key, window[key])
}
// 3. 增改的属性,统统记录在 updatedProps
this.updatedProps.set(key, value)
// 4. 增改的属性,切实修改到 window
window[key] = value
},
get (key) {
const value = window[key]
if (typeof value === 'function') {
return value.bind(window)
}
return window[key]
}
})
}
active() {
// 将 updatedProps 上的属性统统还原至子应用上下文
updatedProps.forEach(key => {
window[key] = updatedProps.get(key)
})
}
inactive() {
// 将所有增加的属性设置为undefined
this.addPropsMap.forEach(key => {
window[key] = undefined
})
// 将所有修改的属性,设置为原值
this.modifiedProps.forEach(key => {
window[key] = this.modifiedProps[key]
})
}
}
四. Proxy多实例沙箱(ProxySandbox)
应用特点:
- 不会切实改变window
- 支持多子应用实例,每个子应用重新实例化一个沙箱
- 实际上完全没必要active和inactive
- 最万能理想,简单的沙箱。
实现思路:
-
维护一个 updateProps Map,用这个对象代替window实体
-
创建 proxy
- set:所有 k-v 都修改到 updateProps 上,不实际修改window上的任何属性
- get:所有 key 都优先从 updateProps 上拿,拿不到再上 window 上拿
class ProxySandbox {
constructor() {
const rawWindow = window;
const fakeWindow = {}
const proxy = new Proxy(fakeWindow, {
set(target, p, value) {
target[p] = value;
return true
},
get(target, p) {
return target[p] || rawWindow[p];
}
});
this.proxy = proxy
}
}
let sandbox1 = new ProxySandbox();
let sandbox2 = new ProxySandbox();
window.a = 1;
((window) => {
window.a = 'hello';
console.log(window.a)
})(sandbox1.proxy);
((window) => {
window.a = 'world';
console.log(window.a)
})(sandbox2.proxy);