关于乾坤js隔离沙箱调研

介绍

沙箱就是Js隔离机制,乾坤有三种Js隔离机制,在源代码中也是以 SnapshotSandbox(快照沙箱)、LegacySandbox(支持单应用的代理沙箱)、ProxySandbox(支持多应用的代理沙箱)三个类名来指代三种不同的隔离机制。为了理解方便,我会用最简单的逻辑最基础的语法来实现三个沙箱,方便我们更快速的理解其中的原理。

SnapshotSandbox(快照沙箱)

javascript 复制代码
//极简版
class SnapshotSandBox{
    windowSnapshot = {};
    modifyPropsMap = {};
    active(){
        for(const prop in window){
            this.windowSnapshot[prop] = window[prop];
        }
        Object.keys(this.modifyPropsMap).forEach(prop=>{
            window[prop] = this.modifyPropsMap[prop];
        });
    }
    inactive(){
        for(const prop in window){
            if(window[prop] !== this.windowSnapshot[prop]){
                this.modifyPropsMap[prop] = window[prop];
                window[prop] = this.windowSnapshot[prop];
            }
        }
    }
}
// 验证:
let snapshotSandBox = new SnapshotSandBox();
snapshotSandBox.active();
window.city = 'Beijing';
console.log("window.city-01:", window.city);
snapshotSandBox.inactive();
console.log("window.city-02:", window.city);
snapshotSandBox.active();
console.log("window.city-03:", window.city);
snapshotSandBox.inactive();

//输出:
//window.city-01: Beijing
//window.city-02: undefined
//window.city-03: Beijing

分析

快照沙箱的核心逻辑很简单,就是在激活沙箱和沙箱失活的时候各做两件事情。沙箱激活就是此时我们的微应用处于运行中,这个阶段有可能对window上的属性进行操作改变;沙箱失活就是此时我们的微应用已经停止了对window的影响。

在沙箱激活的时候:

1.记录window当时的状态(我们把这个状态称之为快照,也就是快照沙箱这个名称的来源)。

2.恢复上一次沙箱失活时记录的沙箱运行过程中对window做的状态改变,也就是上一次沙箱激活后对window做了哪些改变,现在也保持一样的改变。

在沙箱失活的时候:

1.记录window上有哪些状态发生了变化(沙箱自激活开始,到失活的这段时间)。

2.清除沙箱在激活之后在window上改变的状态,从代码可以看出,就是让window此时的属性状态和刚激活时候的window的属性状态进行对比,不同的属性状态就以快照为准,恢复到未改变之前的状态。

从上面可以看出,快照沙箱存在两个重要的问题: 1.会改变全局window的属性,如果同时运行多个微应用,多个应用同时改写window上的属性,势必会出现状态混乱,这也就是为什么快照沙箱无法支持多个微应用同时运行的原因。 2.会通过for(prop in window){}的方式来遍历window上的所有属性,window属性众多,这其实是一件很耗费性能的事情。

缺点: 1.会污染全局的window。 2.并且需要遍历window上的所有属性,性能较差。 3.仅仅允许页面同时运行一个微应用。

LegacySandbox(支持单应用的代理沙箱)

javascript 复制代码
//极简版
class LegacySandBox{
    addedPropsMapInSandbox = new Map();
    modifiedPropsOriginalValueMapInSandbox = new Map();
    currentUpdatedPropsValueMap = new Map();
    proxyWindow;
    setWindowProp(prop, value, toDelete = false){
        if(value === undefined && toDelete){
            delete window[prop];
        }else{
            window[prop] = value;
        }
    }
    active(){
        this.currentUpdatedPropsValueMap.forEach((value, prop)=>this.setWindowProp(prop, value));
    }
    inactive(){
        this.modifiedPropsOriginalValueMapInSandbox.forEach((value, prop)=>this.setWindowProp(prop, value));
        this.addedPropsMapInSandbox.forEach((_, prop)=>this.setWindowProp(prop, undefined, true));
    }
    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.modifiedPropsOriginalValueMapInSandbox.has(prop)){
                    this.modifiedPropsOriginalValueMapInSandbox.set(prop, originalVal);
                }
                this.currentUpdatedPropsValueMap.set(prop, value);
                window[prop] = value;
            },
            get:(target, prop, receiver)=>{
                return target[prop];
            }
        });
    }
}
// 验证:
let legacySandBox = new LegacySandBox();
legacySandBox.active();
legacySandBox.proxyWindow.city = 'Beijing';
console.log('window.city-01:', window.city);
legacySandBox.inactive();
console.log('window.city-02:', window.city);
legacySandBox.active();
console.log('window.city-03:', window.city);
legacySandBox.inactive();
// 输出:
// 输出:
// window.city-01: Beijing
// window.city-02: undefined
// window.city-03: Beijing

分析

其实现的功能和快照沙箱是一模一样的,不同的是,通过三个变量来记住沙箱激活后window发生变化过的所有属性,这样在后续的状态还原时候就不再需要遍历window的所有属性来进行对比,提升了程序运行的性能。但是这仍然改变不了这种机制仍然污染了window的状态的事实,因此也就无法承担起同时支持多个微应用运行的任务。

缺点: 1.同样会污染全局的window。 2.仅仅允许页面同时运行一个微应用。

主要解决问题:但是随着ES6的普及,利用Proxy可以比较良好的解决需要遍历window所有属性而造成性能差的问题,这就诞生了LegacySandbox,可以实现和快照沙箱一样的功能,但是性能更好。

ProxySandbox(支持多应用的代理沙箱)

javascript 复制代码
//极简版
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];
            }
        });
    }
}
// 验证:
let proxySandBox1 = new ProxySandBox();
let proxySandBox2 = new ProxySandBox();
proxySandBox1.active();
proxySandBox2.active();
proxySandBox1.proxyWindow.city = 'Beijing';
proxySandBox2.proxyWindow.city = 'Shanghai';
console.log('active:proxySandBox1:window.city:', proxySandBox1.proxyWindow.city);
console.log('active:proxySandBox2:window.city:', proxySandBox2.proxyWindow.city);
console.log('window:window.city:', window.city);
proxySandBox1.inactive();
proxySandBox2.inactive();
console.log('inactive:proxySandBox1:window.city:', proxySandBox1.proxyWindow.city);
console.log('inactive:proxySandBox2:window.city:', proxySandBox2.proxyWindow.city);
console.log('window:window.city:', window.city);
// 输出:
// active:proxySandBox1:window.city: Beijing
// active:proxySandBox2:window.city: Shanghai
// window:window.city: undefined
// inactive:proxySandBox1:window.city: Beijing
// inactive:proxySandBox2:window.city: Shanghai
// window:window.city: undefined

分析

ProxySandbox,完全不存在状态恢复的逻辑,同时也不需要记录属性值的变化,因为所有的变化都是沙箱内部的变化,和window没有关系,window上的属性自始至终都没有受到过影响。

主要解决问题:支持同时运行多个微应用

最后

LegacySandbox在未来应该会消失,因为LegacySandbox可以做的事情,ProxySandbox都可以做,而SanpsshotSandbox因为向下兼容的原因反而会和ProxySandbox长期并存。

相关推荐
一颗花生米。3 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐013 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19953 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
勿语&4 小时前
Element-UI Plus 暗黑主题切换及自定义主题色
开发语言·javascript·ui
黄尚圈圈4 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水5 小时前
简洁之道 - React Hook Form
前端
正小安7 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch9 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光9 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js