为什么需要JS沙箱

想象一下

当一个应用(比如应用 A)加载时,可能会对 window 对象的属性进行修改或添加。如果不加控制,这些修改会影响到之后加载的其他应用(比如应用 B),就会导致属性读写冲突

所以!对于各应用的 js文件来说,就需要一个独立的环境来执行,防止 window 全局对象发生属性读写冲突,这个独立的执行环境就叫做 JS沙箱

乾坤沙箱

乾坤目前存在三种 JS隔离机制,分别是 SnapshotSandboxLegacySandboxProxySandbox

  1. SnapshotSandbox(快照沙箱)

    这是乾坤最早期的沙箱机制,在每次应用激活和失活时遍历 window 对象的所有属性,记录并恢复其状态 。

    性能很差,浪费内存;优点是可以兼容不支持 Proxy 的旧版浏览器

  2. LegacySandbox(单应用代理沙箱)

    在 SnapshotSandbox 基础上进行优化的一种沙箱机制,通过 ES6 的Proxywindow 对象进行更高效的代理。

    然而,它仍会读写 window 对象,存在全局污染的问题;并且只能支持单个微应用的运行,意味着在一个页面上不能同时运行多个微应用

    事实上,LegacySandbox 在未来应该会消失, 逐渐被能够同时支持多个微应用的 ProxySandbox 所取代

  3. ProxySandbox(多应用代理沙箱)

    是乾坤最先进的一种 JS沙箱隔离机制,通过 Proxy 对象为每个微应用创建了一个独立的虚拟 window。不会操作 window 对象,不存在全局污染的问题;而且在同一页面上也支持多个微应用的同时运行

    缺点则是不兼容不支持 proxy 旧版浏览器

SnapshotSandbox(快照沙箱)

这是乾坤最早期的沙箱机制,其主要目的是通过记录和恢复 window 对象的状态,确保每个微应用在激活和失活时都能够拥有独立的环境

这里我们实现一个简单的快照沙箱机制, 用于隔离不同微应用对全局 window 对象的修改,对应乾坤的源代码可以看这里 - SnapshotSandbox源代码

工作原理

当沙箱激活时, 保存当前 window 的状态到 windowSnapShot,然后恢复到上次失活前的状态 (从 modifyPropsMap 中读取)

当沙箱失活时, 记录所有对 window 对象的修改(与 windowSnapShot 的差异),并将 window 恢复到最初的快照状态

class SnapshotSandbox {
constructor() {
this.windowSnapShot = {}; // 存储 window 对象的初始快照
this.modifyPropsMap = {}; // 存储全局哪些属性被修改了
}
// 激活
active() {
this.windowSnapShot = {};
// 记录应用 A window 初始状态
Object.keys(window).forEach(prop => {
this.windowSnapShot[prop] = window[prop]
})
// 恢复到应用 A 上次失活之前的状态
Object.keys(this.modifyPropsMap).forEach(prop => {
window[prop] = this.modifyPropsMap[prop]
})
}
// 失活
inactive() {
this.modifyPropsMap = {}
Object.keys(window).forEach(prop => {
if (window[prop] !== this.windowSnapShot[prop]) {
this.modifyPropsMap[prop] = window[prop]; // 记录应用A 所做的所有修改
window[prop] = this.windowSnapShot[prop]; // 将 window 恢复到最初状态
}
})
}
}
let sandbox = new SnapshotSandbox();
sandbox.active();
window.a = 100;
window.b = 200;
sandbox.inactive();
console.log(window.a, window.b)
sandbox.active();
console.log(window.a, window.b)
  • 性能很差, 因为需要在每次应用激活和失活时遍历 window 对象的所有属性来记录和恢复其状态。在属性较多或频繁切换应用的情况下,性能瓶颈尤为明显。
  • 浪费内存, 保存 window 对象的完整状态会占用大量内存
  • 优点是可以兼容不支持 Proxy 的旧版浏览器

总的来说,快照沙箱通过拍摄 window 对象的快照来实现状态的隔离,虽然简单有效,但存在性能和内存方面的不足,适用于对浏览器兼容性有要求的场景

LegacySandbox(单应用代理沙箱)

是在 SnapshotSandbox 基础上优化的一种沙箱机制。它利用 ES6 引入的 Proxy 特性来提高性能,并实现类似于快照沙箱的功能

这里我们实现一个简单的单应用代理沙箱机制, 对应乾坤的源代码可以看这里 - LegacySandbox源代码

工作原理

创建一个 fakeWindow 对象,并使用 Proxy 对其进行代理,在 set 拦截器中,addedPropsMap 记录新添加的属性,modifyPropsMap 记录被修改的属性原始值,currentPropsMap 记录所有被添加修改的属性最新值

当沙箱激活时,恢复到上次失活前的状态 (从 currentPropsMap 中读取)

当沙箱失活时,还原修改前的属性值并删除新添加的属性,将 window 恢复到最初状态(从 modifyPropsMapaddedPropsMap 中读取)

class LegacySandbox {
constructor() {
this.modifyPropsMap = new Map() // 存储被修改过的属性原始值
this.addedPropsMap = new Map() // 存储新添加的属性值
this.currentPropsMap = new Map() // 存储所有被修改或新添加的属性最新值 // 创建一个 fakeWindow 对象,并使用 Proxy 对其进行代理
const fakeWindow = Object.create(null)
const proxy = new Proxy(fakeWindow, {
get: (target, key, recevier) => {
return window[key]
},
set: (target, key, value) => {
if (!window.hasOwnProperty(key)) {
this.addedPropsMap.set(key, value) // 新添加的属性
} else if (!this.modifyPropsMap.has(key)) {
this.modifyPropsMap.set(key, window[key]) // 被修改的属性原始值
}
this.currentPropsMap.set(key, value) // 所有的添加修改操作都存储一份最新值
window[key] = value
},
})
this.proxy = proxy
}
// 设置 window 对象的属性
setWindowProp(key, value) {
if (value == undefined) {
delete window[key]
} else {
window[key] = value
}
}
// 激活沙箱
active() {
// 恢复到应用 A 上次失活之前的状态
this.currentPropsMap.forEach((value, key) => {
this.setWindowProp(key, value)
})
}
// 失活沙箱
inactive() {
// 被修改的属性重置为原始值
this.modifyPropsMap.forEach((value, key) => {
this.setWindowProp(key, value)
})
// 移除新添加的属性
this.addedPropsMap.forEach((value, key) => {
this.setWindowProp(key, undefined)
})
}
}
let sandbox = new LegacySandbox()
sandbox.proxy.a = 100
console.log(window.a, sandbox.proxy.a)
sandbox.inactive()
console.log(window.a, sandbox.proxy.a)
sandbox.active()
console.log(window.a, sandbox.proxy.a)
  • 性能优化:通过 Proxywindow 对象进行代理 ,避免了遍历 window 的性能开销
  • 全局污染:尽管使用了 Proxy,LegacySandbox 依然在全局 window 上进行操作,还是会污染全局的 window
  • 单应用支持:和 SnapshotSandbox 一样,LegacySandbox 仅支持单个微应用的运行,无法在同一页面同时隔离多个微应用

事实上,LegacySandbox 在未来应该会消失, 逐渐被能够同时支持多个微应用的 ProxySandbox 所取代

ProxySandbox(多应用代理沙箱)

ProxySandbox 也是一种基于 Proxy 对象实现的沙箱机制,它能够支持在同一页面上运行多个微应用,同时保证每个应用对全局环境(window 对象)的修改是隔离的

这里我们实现一个简单的多应用代理沙箱机制, 对应乾坤的源代码可以看这里 - ProxySandbox源代码

工作原理

创建一个 fakeWindow 对象,并使用 Proxy 对其进行代理

当沙箱激活时,所有的修改都被限制在 fakeWindow 内,而不影响真实的 window 对象

当沙箱失活时,将 this.running 设置为 false, 沙箱不再拦截修改操作

class ProxySandbox {
constructor() {
this.running = false
// 使用 Proxy 对 fakeWindow 进行代理
const fakeWindow = Object.create(null)
this.proxy = new Proxy(fakeWindow, {
get: (target, key) => {
return key in target ? target[key] : window[key]
},
set: (target, key, value) => {
if (this.running) {
target[key] = value // 将修改操作应用到 fakeWindow 上,而不是真实的 window 对象
}
return true
},
})
}
active() {
if (!this.running) this.running = true
}
inactive() {
this.running = false
}
}
let sandbox1 = new ProxySandbox()
let sandbox2 = new ProxySandbox()
sandbox1.active()
sandbox2.active()
sandbox1.proxy.a = 100
sandbox2.proxy.a = 100
console.log(sandbox1.proxy.a, sandbox2.proxy.a)
sandbox1.inactive()
sandbox2.inactive()
sandbox1.proxy.a = 200
sandbox2.proxy.a = 200
console.log(sandbox1.proxy.a, window.a)
console.log(sandbox2.proxy.a, window.a)
  • 多应用支持:每个应用拥有自己的虚拟 window,这使得多个微应用可以在同一页面中独立运行而不会相互影响
  • 高效性能:通过 Proxywindow 对象进行代理,能够在不修改原始 window 的情况下实现沙箱隔离,性能更高。
  • 依赖现代浏览器:依赖于 ES6 的 Proxy,不支持较旧的浏览器

ProxySandbox 通过 Proxy 为每个微应用创建了一个独立的虚拟 window,有效地隔离了微应用之间的全局状态。它是现代微前端架构中实现多应用支持和环境隔离的关键技术之一。通过这种方式,开发者可以在同一页面中并行运行多个微应用,而不用担心全局变量污染或应用间的干扰。

参考文档

GitHub - zuopf769/qiankun-js-sandbox: 乾坤的JS沙箱隔离机制原理剖析

GitHub - careyke/frontend_knowledge_structure: qiankun中JS沙箱的实现

微前端01 : 乾坤的Js隔离机制原理剖析(快照沙箱、两种代理沙箱)

qiankun 的 JS 沙箱隔离机制的更多相关文章

  1. JVM探究(一)谈谈双亲委派机制和沙箱安全机制

    JVM探究 请你谈谈你对JVM的理解?java8虚拟机和之前的变化gengxin? 什么是OOM,什么是栈溢出StackOverFlowError JVM的常用调优参数有哪些? 内存快转如何抓取,怎么 ...

  2. J V M(三)沙箱安全机制

    沙箱安全机制 Java安全模型的核心就是Java沙箱(sandbox)什么是沙箱? 沙箱是一个限制程序运行的环境.沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本 ...

  3. 关于JS 沙箱(转)

    javascript中的沙箱并非传统意义上的沙箱,只是一种语法上的hack写法而已,javascript中处理模块依赖关系的闭包被称之为沙箱,和 ajax一样,这种sandbox coding风格是一 ...

  4. 关于js内部运行机制的一本好书

    读<单页Web应用一书>,第二章讲了js内部运行机制,感觉棒极了.之前读<你不知道的js>,看的云里雾里,似懂非懂.没想到单页Web一书将此内容讲的如此通俗易懂,好多困惑已久的 ...

  5. JS的解析机制

    JS的解析机制,是JS的又一大重点知识点,在面试题中更经常出现,今天就来唠唠他们的原理.首先呢,我们在我们伟大的浏览器中,有个叫做JS解析器的东西,它专门用来读取JS,执行JS.一般情况是存在作用域就 ...

  6. JS的运行机制

    代码块: JS中的代码块是指由<script>标签分割的代码段.JS是按照代码块来进行编译和执行的,代码块间相互独立(即就算代码块1出错,但不影响代码块2的加载和执行),但变量和方法共享. ...

  7. Hibernate逍遥游记-第15章处理并发问题-001事务并发问题及隔离机制介绍

    1. 2.第一类丢失更新 3.脏读 4.虚读.幻读 5.不可重复读 6.第二类丢失更新 7.数据库的锁机制 8.数据库事务的隔离机制

  8. 试着讲清楚:js代码运行机制

    一. js运行机制 js执行引擎 经常看文章的说到js是带线程的,其实这个说法非常的模糊,准确的是js执行引擎是单线程的,js执行引擎就是js代码的执行器,有了这个概念就可以下来说说js是如何运行的了 ...

  9. 浅析JS异步执行机制

    前言 JS异步执行机制具有非常重要的地位,尤其体现在回调函数和事件等方面.本文将针对JS异步执行机制进行一个简单的分析. 从一份代码讲起 下面是两个经典的JS定时执行函数,这两个函数的区别相信对JS有 ...

  10. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

随机推荐

  1. Pointer Event Api-整合鼠标事件、触摸和触控笔事件

    Pointer Events API 是Hmtl5的事件规范之一,它主要目的是用来将鼠标(Mouse).触摸(touch)和触控笔(pen)三种事件整合为统一的API. Pointer Event P ...

  2. Spring框架漏洞总结

    目录 SpEL注入攻击 Spring H2 Database Console未授权访问 Spring Security OAuth2远程命令执行漏洞(CVE-2016-4977) Spring Web ...

  3. 一个 tomcat 下如何部署多个项目?附详细步骤

    一个tomcat下如何部署多个项目?Linux跟windows系统下的步骤都差不多,以下linux系统下部署为例.windows系统下部署同理. 一.不修改端口,部署多个项目 清楚tomcat目录结构 ...

  4. [Tkey] Transport Nekomusume II

    CL-20 考虑定义一条有向边 \(u\rightarrow v\) 的意义为 \(u\) 把窝让给了 \(v\),那么每个点一定入度为 \(1\),所有的边会形成一个外向基环树森林. 贪心地把猫娘按 ...

  5. .NET 工具库高效生成 PDF 文档

    前言 QuestPDF 是一个开源 .NET 库,用于生成 PDF 文档.使用了C# Fluent API方式可简化开发.减少错误并提高工作效率.利用它可以轻松生成 PDF 报告.发票.导出文件等. ...

  6. 【赵渝强老师】什么是Spark SQL?

    一.Spark SQL简介 Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个编程抽象叫做DataFrame并且作为分布式SQL查询引擎的作用. 为什么要学习Spark SQL? ...

  7. “全栈合一 智慧运维”智和网管平台SugarNMS V9版本发布

    以"管控万物 无所不能 无处不"在为产品创新理念,智和信通打造"全栈式"网络安全运维平台-智和网管平台SugarNMS.立足数字化.智能化.可视化.自动化,整合 ...

  8. python——celery异常consumer: Cannot connect to redis://127.0.0.1:6379/1: MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk.

    1.检查 Redis 日志: 查看 Redis 的日志文件(通常位于 /var/log/redis/redis-server.log 或者根据你的配置文件中指定的位置),以获取有关错误原因的详细信息. ...

  9. USB协议详解第10讲(USB描述符-报告描述符)

    1.报告描述符的概念和作用 开门见山,报告描述符就是描述报告(HID接口上传输事务中的数据)的一组数据结构. 首先大家可能会问,报告又是什么?我们前面讲过,USB主机一般是以中断的方式向HID设备发送 ...

  10. 随心所动,厂商的CPU核管理策略介绍

    一.引文 随着CPU架构的发展,工艺的升级,带来性能提升,能效的提升(同性能下).但是由于极限性能的增加,也带来了peak功耗的增加(大部分情况下,能效比的提升无法抵消这部分),CPU功耗优化一直是广 ...