为什么需要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. 小tips:npm与npx的区别

    npm npm是Node.js的软件包管理器,其目标是自动化的依赖性和软件包管理. 这意味着,可以在package.json文件中为项目指定所有依赖项(软件包),当需要为其安装依赖项时,只要运行npm ...

  2. ASP.NET Core Library – scriban (Template Engine)

    前言 有些项目会需要让 end user 写模板 (rich text) 同时又需要做一些 data binding. 这几乎是 programmer 的工作了... 在 C#, 大可以使用 Razo ...

  3. MyBatis——使用Mapper代理开发

    使用 Mapper 代理开发方式完成入门案例   1.定义与SQL映射文件同名的Mapper接口,并将 Mapper 接口和SQL映射文件放置在同一目录下       (企业开发中,通常是将配置文件统 ...

  4. 【翻译】实现 Blocked Floyd-Warshall 用于解决所有对最短路径问题 C# 实现

    介绍 在之前的帖子中,我们实现了Floyd-Warshall(弗洛伊德-沃沙尔算法)(四种变体)以及路由重建算法.在这些帖子中,我们探讨了所有对最短路径问题的基本概念.内存中的数据表示.并行性.向量化 ...

  5. 《Vue.js 设计与实现》读书笔记 - 第14章、内建组件和模块

    第14章.内建组件和模块 14.1 KeepAlive 组件的实现原理 KeepAlive 一词借鉴了 HTTP 协议. KeepAlive 组件可以避免组件被频繁的销毁/重建.本质是缓存管理,再加上 ...

  6. 《Vue.js 设计与实现》读书笔记 - 第6章、原始值的响应式方案 & 响应式总结

    第6章.原始值的响应式方案 6.1 引入 ref 的概念 既然原始值无法使用 Proxy 我们就只能把原始值包裹起来. function ref(val) { const wrapper = { va ...

  7. 使用GRUB Multiboot2引导自制操作系统

    使用GRUB Multiboot2引导自制操作系统 前言 之前花了一周时间,从头学习了传统 BIOS 的启动流程.惊讶于背后丰富的技术细节的同时,也感叹 x86 架构那厚重的历史包袱.毕竟,谁能想到, ...

  8. glance对接ceph

    目录 glance对接ceph 1. 上传镜像 2. 对接ceph 2.1 创建池 2.2 创建用户 2.3 下发ceph文件 2.4 修改globals文件 2.5 更新glance配置 3. 上传 ...

  9. 谈一谈你对vue指令的理解

    vue指令的本质是给 html 标签新增一些属性 : vue的指令可以分为 3 中类型 : 1. 用于渲染数据的指令,比如 v-for ,v-if ,v-show : 2. 用来交互的指令 ,v-on ...

  10. 时隔半年 DotNetGuide 已突破了 6.6K + Star,持续更新,欢迎更多小伙伴PR投稿!

    前言 记得今年5月份的时候 DotNetGuide GitHub才突破5k Star,经过持续不断地输出时隔半年 DotNetGuide 已突破了 6.6K + Star!并且由我创建的DotNetG ...