searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

JavaScript沙箱机制探索

2023-06-02 05:38:39
29
0

使用微前端方案时,我们要确保应用之间全局变量不能互相影响。比如,子应用 A 给 window 对象定义了新的属性 - globalState,子应用 B 也给 window 对象定义了新的属性 globalState,那我们就需要确保子应用 A 和子应用 B 能获取到各自定义的 globalState。最容易想到的方法就是通过人为约定添加命名前缀的方式来进行 js 隔离,如子应用 A 给 window 对象定义了新的属性 - A_globalState,子应用 B 给 window 对象定义了新的属性 B_globalState。这种方式低效不说,还容易引发 bug。

这个时候, 我们可以引入沙盒, 即sandbox, 这个计算机世界的常见概念, 来帮助我们解决这个问题。沙盒是计算机安全领域中的一种安全机制,可以为运行中的程序提供隔离的环境. 运用到微前端的场景, 我们可以尝试通过沙盒为每个子应用提供隔离的运行环境,保证子应用的 js 代码在执行时使用的全局变量都是独属于当前子应用的。

本文总结归纳了几种常见的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];
            }
        }
    }
}

顾名思义, 就是给window对象打一个快照, 这样就可以随时对window对象进行还原. 主要步骤如下:

在沙箱激活的时候:

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

在沙箱失活的时候

  • 记录window上有哪些状态发生了变化
  • 清除沙箱在激活之后在window上改变的状态

快照沙箱存在两个重要的问题:

  • 会改变全局window的属性,如果同时运行多个微应用,多个应用同时改写window上的属性,势必会出现状态混乱,这也就是为什么快照沙箱无法支持多各微应用同时运行的原因
  • 会通过for(prop in window){}的方式来遍历window上的所有属性,window属性众多,这其实是一件很耗费性能的事情

proxy代理沙箱

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];
            }
        });
    }
}

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

支持多应用的代理沙箱

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];
            }
        });
    }
}

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

以上总结了三个js沙盒隔离机制, 并给出了极简代码实现, 相信读者朋友如果理解了上面的思路,就可以说已经理解了乾坤的Js隔离机制。

0条评论
0 / 1000
y****n
2文章数
0粉丝数
y****n
2 文章 | 0 粉丝
y****n
2文章数
0粉丝数
y****n
2 文章 | 0 粉丝
原创

JavaScript沙箱机制探索

2023-06-02 05:38:39
29
0

使用微前端方案时,我们要确保应用之间全局变量不能互相影响。比如,子应用 A 给 window 对象定义了新的属性 - globalState,子应用 B 也给 window 对象定义了新的属性 globalState,那我们就需要确保子应用 A 和子应用 B 能获取到各自定义的 globalState。最容易想到的方法就是通过人为约定添加命名前缀的方式来进行 js 隔离,如子应用 A 给 window 对象定义了新的属性 - A_globalState,子应用 B 给 window 对象定义了新的属性 B_globalState。这种方式低效不说,还容易引发 bug。

这个时候, 我们可以引入沙盒, 即sandbox, 这个计算机世界的常见概念, 来帮助我们解决这个问题。沙盒是计算机安全领域中的一种安全机制,可以为运行中的程序提供隔离的环境. 运用到微前端的场景, 我们可以尝试通过沙盒为每个子应用提供隔离的运行环境,保证子应用的 js 代码在执行时使用的全局变量都是独属于当前子应用的。

本文总结归纳了几种常见的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];
            }
        }
    }
}

顾名思义, 就是给window对象打一个快照, 这样就可以随时对window对象进行还原. 主要步骤如下:

在沙箱激活的时候:

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

在沙箱失活的时候

  • 记录window上有哪些状态发生了变化
  • 清除沙箱在激活之后在window上改变的状态

快照沙箱存在两个重要的问题:

  • 会改变全局window的属性,如果同时运行多个微应用,多个应用同时改写window上的属性,势必会出现状态混乱,这也就是为什么快照沙箱无法支持多各微应用同时运行的原因
  • 会通过for(prop in window){}的方式来遍历window上的所有属性,window属性众多,这其实是一件很耗费性能的事情

proxy代理沙箱

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];
            }
        });
    }
}

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

支持多应用的代理沙箱

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];
            }
        });
    }
}

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

以上总结了三个js沙盒隔离机制, 并给出了极简代码实现, 相信读者朋友如果理解了上面的思路,就可以说已经理解了乾坤的Js隔离机制。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0