文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

前端JS沙箱的实现方法有哪些

2023-06-20 14:31

关注

这篇文章主要介绍了前端JS沙箱的实现方法有哪些,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

前言

在微前端领域当中,沙箱是很重要的一件事情。像微前端框架single-spa没有实现js沙箱,我们在构建大型微前端应用的时候,很容易造成一些变量的冲突,对应用的可靠性面临巨大的风险。在微前端当中,有一些全局对象在所有的应用中需要共享,如document,location,等对象。子应用开发的过程中可能是多个团队在做,很难约束他们使用全局变量。有些页面可能会有多个不同的子应用,需要我们支持多沙箱,每个沙箱需要有加载,卸载,在恢复的能力。

iframe实现沙箱

在前端中,有一个比较重要的html标签iframe,实际上,我们可以通过iframe对象,把原生浏览器对象通过contentWindow取出来,这个对象天然具有所有的属性,而且与主应用的环境隔离。下面我们通过代码看下

let iframe  = document.createElement('iframe',{src:'about:blank'});document.body.appendChild(iframe);const sandboxGlobal = iframe.contentWindow;

注意:只有同域的ifame才能取出对应的contentWindow, iframe的src设置为about:blank,可以保证一定是同域的,也不会发生资源加载,参考iframe src

在前言中我们提到,微前端除了有一个隔离的window环境外,其实还需要共享一些全局对象,这时候我们可以用代理去实现。下面我们通过代码看下

class SandboxWindow {        constructor(context, frameWindow) {                return new Proxy(frameWindow, {            get(target, name) {                if (name in context) { // 优先使用共享对象                    return context[name];                }                return target[name];            },            set(target, name, value) {                if (name in context) { // 修改共享对象的值                    return context[name] = value;                }                target[name] = value;            }        })    }}// 需要全局共享的变量const context = { document:window.document, history: window.history }// 创建沙箱const newSandboxWindow = new SandboxWindow(context, sandboxGlobal); // 判断沙箱上的对象和全局对象是否相等console.log('equal',newSandboxWindow.document === window.document)newSandboxWindow.abc = '1'; //在沙箱上添加属性console.log(window.abc);   // 在全局上查看属性console.log(newSandboxWindow.abc) //在沙箱上查看属性

我们运行起来,看下结果

前端JS沙箱的实现方法有哪些

以上我们利用iframe沙箱可以实现以下特性:

diff方式实现沙箱

在不支持代理的浏览器中,我们可以通过diff的方式实习沙箱。在应用运行的时候保存一个快照window对象,将当前window对象的全部属性都复制到快照对象上,子应用卸载的时候将window对象修改做个diff,将不同的属性用个modifyMap保存起来,再次挂载的时候再加上这些修改的属性。代码如下:

class DiffSandbox {  constructor(name) {    this.name = name;    this.modifyMap = {}; // 存放修改的属性    this.windowSnapshot = {};  }  active() {    // 缓存active状态的沙箱    this.windowSnapshot = {};    for (const item in window) {      this.windowSnapshot[item] = window[item];    }    Object.keys(this.modifyMap).forEach(p => {      window[p] = this.modifyMap[p];    })  }  inactive() {    for (const item in window) {      if (this.windowSnapshot[item] !== window[item]) {        // 记录变更        this.modifyMap[item] = window[item];        // 还原window        window[item] = this.windowSnapshot[item];      }    }  }}const diffSandbox = new DiffSandbox('diff沙箱');diffSandbox.active();  // 激活沙箱window.a = '1'console.log('开启沙箱:',window.a);diffSandbox.inactive(); //失活沙箱console.log('失活沙箱:', window.a);diffSandbox.active();   // 重新激活console.log('再次激活', window.a);

我们运行一下,查看结果

前端JS沙箱的实现方法有哪些

这种方式也无法支持多实例,因为运行期间所有的属性都是保存在window上的。

基于代理(Proxy)实现单实例沙箱

在ES6当中,我们可以通过代理(Proxy)实现对象的劫持。基本实录也是通过window对象的修改进行记录,在卸载时删除这些记录,在应用再次激活时恢复这些记录,来达到模拟沙箱环境的目的。代码如下

// 修改window属性的公共方法const updateWindowProp = (prop, value, isDel) => {    if (value === undefined || isDel) {        delete window[prop];    } else {        window[prop] = value;    }}class ProxySandbox {    active() {        // 根据记录还原沙箱        this.currentUpdatedPropsValueMap.forEach((v, p) => updateWindowProp(p, v));    }    inactive() {        // 1 将沙箱期间修改的属性还原为原先的属性        this.modifiedPropsMap.forEach((v, p) => updateWindowProp(p, v));        // 2 将沙箱期间新增的全局变量消除        this.addedPropsMap.forEach((_, p) => updateWindowProp(p, undefined, true));    }    constructor(name) {        this.name = name;        this.proxy = null;        // 存放新增的全局变量        this.addedPropsMap  = new Map();         // 存放沙箱期间更新的全局变量        this.modifiedPropsMap = new Map();        // 存在新增和修改的全局变量,在沙箱激活的时候使用        this.currentUpdatedPropsValueMap = new Map();        const { addedPropsMap, currentUpdatedPropsValueMap, modifiedPropsMap } = this;        const fakeWindow = Object.create(null);        const proxy = new Proxy(fakeWindow, {            set(target, prop, value) {                if (!window.hasOwnProperty(prop)) {                    // 如果window上没有的属性,记录到新增属性里                    // debugger;                    addedPropsMap.set(prop, value);                } else if (!modifiedPropsMap.has(prop)) {                    // 如果当前window对象有该属性,且未更新过,则记录该属性在window上的初始值                    const originalValue = window[prop];                    modifiedPropsMap.set(prop, originalValue);                }                // 记录修改属性以及修改后的值                currentUpdatedPropsValueMap.set(prop, value);                // 设置值到全局window上                updateWindowProp(prop, value);                return true;            },            get(target, prop) {                return window[prop];            },        });        this.proxy = proxy;    }}const newSandBox = new ProxySandbox('代理沙箱');const proxyWindow = newSandBox.proxy;proxyWindow.a = '1'console.log('开启沙箱:', proxyWindow.a, window.a);newSandBox.inactive(); //失活沙箱console.log('失活沙箱:', proxyWindow.a, window.a);newSandBox.active(); //失活沙箱console.log('重新激活沙箱:', proxyWindow.a, window.a);

我们运行代码,看下结果

前端JS沙箱的实现方法有哪些

这种方式同一时刻只能有一个激活的沙箱,否则全局对象上的变量会有两个以上的沙箱更新,造成全局变量冲突。

基于代理(Proxy)实现多实例沙箱

在单实例的场景总,我们的fakeWindow是一个空的对象,其没有任何储存变量的功能,微应用创建的变量最终实际都是挂载在window上的,这就限制了同一时刻不能有两个激活的微应用。

class MultipleProxySandbox {    active() {        this.sandboxRunning = true;    }    inactive() {        this.sandboxRunning = false;    }        constructor(name, context = {}) {        this.name = name;        this.proxy = null;        const fakeWindow = Object.create({});        const proxy = new Proxy(fakeWindow, {            set: (target, name, value) => {                if (this.sandboxRunning) {                    if (Object.keys(context).includes(name)) {                        context[name] = value;                    }                    target[name] = value;                }            },            get: (target, name) => {                // 优先使用共享对象                if (Object.keys(context).includes(name)) {                    return context[name];                }                return target[name];            }        })        this.proxy = proxy;    }}const context = { document: window.document };const newSandBox1 = new MultipleProxySandbox('代理沙箱1', context);newSandBox1.active();const proxyWindow1 = newSandBox1.proxy;const newSandBox2 = new MultipleProxySandbox('代理沙箱2', context);newSandBox2.active();const proxyWindow2 = newSandBox2.proxy;console.log('共享对象是否相等', window.document === proxyWindow1.document, window.document ===  proxyWindow2.document);proxyWindow1.a = '1'; // 设置代理1的值proxyWindow2.a = '2'; // 设置代理2的值window.a = '3';  // 设置window的值console.log('打印输出的值', proxyWindow1.a, proxyWindow2.a, window.a);newSandBox1.inactive(); newSandBox2.inactive(); // 两个沙箱都失活proxyWindow1.a = '4'; // 设置代理1的值proxyWindow2.a = '4'; // 设置代理2的值window.a = '4';  // 设置window的值console.log('失活后打印输出的值', proxyWindow1.a, proxyWindow2.a, window.a);newSandBox1.active(); newSandBox2.active(); // 再次激活proxyWindow1.a = '4'; // 设置代理1的值proxyWindow2.a = '4'; // 设置代理2的值window.a = '4';  // 设置window的值console.log('失活后打印输出的值', proxyWindow1.a, proxyWindow2.a, window.a);

运行代码,结果如下:

前端JS沙箱的实现方法有哪些

这种方式同一时刻只能有一个激活的多个沙箱,从而实现多实例沙箱。

感谢你能够认真阅读完这篇文章,希望小编分享的“前端JS沙箱的实现方法有哪些”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯