前端技术探索 - 你不知道的JS 沙箱隔离

在了解了 JavaScript 沙箱的「前世今生」之后,我们将目光投回本文的主角——Web Worker 身上。

正如本文开头所说,Web Worker 子线程的形式也是一种天然的沙箱隔离,理想的方式,是借鉴 Browser VM 的前段思路,在编译阶段通过 Webpack 插件为每个子应用包裹一层创建 Worker 对象的代码,让子应用运行在其对应的单个 Worker 实例中,比如:

WRAP_WORKER(/* 打包代码 */ });

function WRAP_WORKER(appCode) {

var blob = new Blob([appCode]);

var appWorker = new Worker(window.URL.createObjectURL(blob));

}

但在了解过微前端下 JavaScript 沙箱的实现过程后,我们不难发现几个在 Web Worker 下去实现微前端场景的 JavaScript 沙箱必然会遇到的几个难题:

  1. 出于线程安全设计考虑,Web Worker 不支持 DOM 操作,必须通过 postMessage 通知 UI 主线程来实现。

  2. Web Worker 无法访问 window、document 之类的浏览器全局对象。

其他诸如 Web Worker 无法访问页面全局变量和函数、无法调用 alert、confirm 等 BOM API 等问题,相对于无法访问 window、document 全局对象已经是小问题了。不过可喜的是,Web Worker 中可以正常使用 setTimeout、setInterval 等定时器函数,也仍能发送 ajax 请求。

所以,当先要解决问题,便是在单个 Web Worker 实例中执行 DOM 操作的问题了。首先我们有一个大前提:Web Worker 中无法渲染 DOM,所以,我们需要基于实际的应用场景,将 DOM 操作进行拆分。

React Worker DOM

因为我们微前端架构中的子应用局限在 React 技术栈下,我先将目光放在了基于 React 框架的解决方案上。

在 React 中,我们知道其将渲染阶段分为对 DOM 树的改变进行 Diff 和实际渲染改变页面 DOM 两个阶段这一基本事实,那能不能将 Diff 过程置于 Web Worker 中,再将渲染阶段通过 postMessage 与主线程进行通信后放在主线程进行呢?简单一搜,颇为汗颜,已经有大佬在 5、6 年前就有尝试了。这里我们可以参考下 react-worker-dom 的开源代码。

react-worker-dom 中的实现思路很清晰。其在 common/channel.js 中统一封装了子线程和主线程互相通信的接口和序列化通信数据的接口,然后我们可以看到其在 Worker 下实现 DOM 逻辑处理的总入口文件在 worker 目录下,从该入口文件顺藤摸瓜,可以看到其实现了计算 DOM 后通过 postMessage 通知主线程进行渲染的入口文件 WorkerBridge.js 以及其他基于 React 库实现的 DOM 构造、Diff 操作、生命周期 Mock 接口等相关代码,而接受渲染事件通信的入口文件在 page 目录下,该入口文件接受 node 操作事件后再结合 WorkerDomNodeImpl.js 中的接口代码实现了 DOM 在主线程的实际渲染更新。

简单做下总结。基于 React 技术栈,通过在 Web Worker 下实现 Diff 与渲染阶段的进行分离,可以做到一定程度的 DOM 沙箱,但这不是我们想要的微前端架构下的 JavaScript 沙箱。先不谈拆分 Diff 阶段与渲染阶段的成本与收益比,首先,基于技术栈框架的特殊性所做的这诸多努力,会随着这个框架本身版本的升级存在着维护升级难以掌控的问题;其次,假如各个子应用使用的技术栈框架不同,要为这些不同的框架分别封装适配的接口,扩展性和普适性弱;最后,最为重要的一点,这种方法暂时还是没有解决 window 下资源共享的问题,或者说,只是启动了解决这个问题的第一步。

接下来,我们先继续探讨 Worker 下实现 DOM 操作的另外一种方案。window 下资源共享的问题我们放在其后再作讨论。

AMP WorkerDOM

在我开始纠结于如 react-worker-dom 这种思路实际落地开发的诸多「天堑」问题的同时,浏览过其他 DOM 框架因为同样具备插件机制偶然迸进了我的脑海,它是 Google 的 AMP。

AMP 开源项目 中除了如 amphtml 这种通用的 Web 组件框架,还有很多其他工程采用了 Shadow DOM、Web Component 等新技术,在项目下简单刷了一眼后,我欣喜地看到了工程 worker-dom。

粗略翻看下 worker-dom 源码,我们在 src 根目录下可以看到 main-thread 和 worker-thread 两个目录,分别打开看了下后,可以发现其实现拆分 DOM 相关逻辑和 DOM 渲染的思路和上面的 react-worker-dom 基本类似,但 worker-dom 因为和上层框架无关,其下的实现更为贴近 DOM 底层。

先看 worker-thread DOM 逻辑层的相关代码,可以看到其下的 dom 目录 下实现了基于 DOM 标准的所有相关的节点元素、属性接口、document 对象等代码,上一层目录中也实现了 Canvas、CSS、事件、Storage 等全局属性和方法。

接着看 main-thread,其关键功能一方面是提供加载 worker 文件从主线程渲染页面的接口,另一方面可以从 worker.ts 和 nodes.ts 两个文件的代码来理解。

在 worker.ts 中像我最初所设想的那样包裹了一层代码,用于自动生成 Worker 对象,并将代码中的所有 DOM 操作都代理到模拟的 WorkerDOM 对象上:

const code = `

‘use strict’;

(function(){

${workerDOMScript}

self[‘window’] = self;

var workerDOM = WorkerThread.workerDOM;

WorkerThread.hydrate(

workerDOM.document,

${JSON.stringify(strings)},

${JSON.stringify(skeleton)},

${JSON.stringify(cssKeys)},

${JSON.stringify(globalEventHandlerKeys)},

[ w i n d o w . i n n e r W i d t h ,   {window.innerWidth},  window.innerWidth, {window.innerHeight}],

${JSON.stringify(localStorageInit)},

${JSON.stringify(sessionStorageInit)}

);

workerDOM.document${TransferrableKeys.observe};

Object.keys(workerDOM).forEach(function(k){self[k]=workerDOM[k]});

}).call(self);

${authorScript}

//# sourceURL=${encodeURI(config.authorURL)}`;

this[TransferrableKeys.worker] = new Worker(URL.createObjectURL(new Blob([code])));

在 nodes.ts 中,实现了真实元素节点的构造和存储(基于存储数据结构是否以及如何在渲染阶段有优化还需进一步研究源码)。

同时,在 transfer 目录下的源码,定义了逻辑层和 UI 渲染层的消息通信的规范。

总的来看,AMP WorkerDOM 的方案抛弃了上层框架的约束,通过从底层构造了 DOM 所有相关 API 的方式,真正做到了与框架技术栈无关。它一方面完全可以作为上层框架的底层实现,来支持各种上层框架的二次封装迁移(如工程 amp-react-prototype),另一方面结合了当前主流 JavaScript 沙箱方案,通过模拟 window、document 全局方法的并代理到主线程的方式实现了部分的 JavaScript 沙箱隔离(暂时没看到路由隔离的相关代码实现)。

当然,从我个人角度来看,AMP WorkerDOM 也有其当前在落地上一定的局限性。一个是对当前主流上层框架如 Vue、React 等的迁移成本及社区生态的适配成本,另一个是其在单页应用下的尚未看到有相关实现方案,在大型 PC 微前端应用的支持上还无法找到更优方案。

其实,在了解完 AMP WorkerDOM 的实现方案之后,基于 react-worker-dom 思路的后续方案也可以有个大概方向了:渲染通信的后续过程,可考虑结合 Browser VM 的相关实现,在生成 Worker 对象的同时,也生成一个 iframe 对象,然后将 DOM 下的操作都通过 postMessage 发送到主线程后,以与其绑定的 iframe 兑现来执行,同时,通过代理将具体的渲染实现再转发给原 WorkerDomNodeImpl.js 逻辑来实现 DOM 的实际更新。

小结与一些个人前瞻


首先聊一聊个人的一些总结。Web Worker 下实现微前端架构下的 JavaScript 沙箱最初是出于一点个人灵光的闪现,在深入调研后,虽然最终还是因为这样那样的问题导致在方案落地上无法找到最优解从而放弃采用社区通用方案,但仍不妨碍我个人对 Web Worker 技术在实现插件类沙箱应用上的持续看好。插件机制在前端领域一直是津津乐道的一种设计,从 Webpack 编译工具到 IDE 开发工具,从 Web 应用级的实体插件到应用架构设计中插件扩展设计,结合 WebAssembly 技术,Web Worker 无疑将在插件设计上占据举足轻重的地位。

其次是一些个人的一些前瞻思考。其实从 Web Worker 实现 DOM 渲染的调研过程中可以看到,基于逻辑与 UI 分离的思路,前端后续的架构设计有很大机会能够产生一定的变革。目前不管是盛行的 Vue 还是 React 框架,其框架设计不论是 MVVM 还是结合 Redux 之后的 Flux,其本质上仍旧还是由 View 层驱动的框架设计(个人浅见),其具备灵活性的同时也产生着性能优化、大规模项目层级升上后的协作开发困难等问题,而基于 Web Worker 的逻辑与 UI 分离,将促使数据获取、处理、消费整个流程的进一步的业务分层,从而固化出一整套的 MVX 设计思路。

当然,以上这些我个人还处于初步调研的阶段,不成熟之处还需多加琢磨。且听之,后续再实践之。

关于本文

作者:ES2049 / 靳志凯

https://segmentfault.com/a/1190000039795656

The End

欢迎自荐投稿到《前端技术江湖》,如果你觉得这篇内容对你挺有启发,记得点个 **「在看」**哦

点个『在看』支持下 

Vue 面试题

1.Vue 双向绑定原理
2.描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?
3.你是如何理解 Vue 的响应式系统的?
4.虚拟 DOM 实现原理
5.既然 Vue 通过数据劫持可以精准探测数据变化,为什么还需要虚拟 DOM 进行 diff 检测差异?
6.Vue 中 key 值的作用?
7.Vue 的生命周期
8.Vue 组件间通信有哪些方式?
9.watch、methods 和 computed 的区别?
10.vue 中怎么重置 data?
11.组件中写 name 选项有什么作用?
12.vue-router 有哪些钩子函数?
13.route 和 router 的区别是什么?
14.说一下 Vue 和 React 的认识,做一个简单的对比
15.Vue 的 nextTick 的原理是什么?
16.Vuex 有哪几种属性?
17.vue 首屏加载优化
18.Vue 3.0 有没有过了解?
19.vue-cli 替我们做了哪些工作?

算法

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  1. 冒泡排序
  2. 选择排序
  3. 快速排序
  4. 二叉树查找: 最大值、最小值、固定值
  5. 二叉树遍历
  6. 二叉树的最大深度
  7. 给予链表中的任一节点,把它删除掉
  8. 链表倒叙
  9. 如何判断一个单链表有环
  10. 给定一个有序数组,找出两个数相加为一个目标数

由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!有需要的程序猿(媛)可以帮忙点赞+评论666

小值、固定值
5. 二叉树遍历
6. 二叉树的最大深度
7. 给予链表中的任一节点,把它删除掉
8. 链表倒叙
9. 如何判断一个单链表有环
10. 给定一个有序数组,找出两个数相加为一个目标数

[外链图片转存中…(img-xlb7dKQH-1714574815460)]

由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!有需要的程序猿(媛)可以帮忙点赞+评论666

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值