<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
相信大家也知道 qiankun 有 SnapshotSandbox, LegacySandbox 和 ProxySandbox 這些沙箱,而它們又可以分為單例和多例兩種模式,網上也有很多文章對其進行介紹。
但這些文章的關注點都是沙箱的環境恢復做的事,那 JS 的隔離到底是怎麼做到的呢?
換個問法,當我寫 window.a = 1 的時候,a 是怎麼被掛載到這些 XXXSandbox 上的呢?又或者我直接雲修改 window.a = 123 時,JS 沙箱到底是怎麼隔離這個 a 的呢?
總不能這樣吧:
window = window.sandbox window.a = 1 // window.sandbox.a = 1
這篇文章就來簡單聊聊 qiankun 沙箱那些事。
這裡我們還是稍微複習一下 qiankun 的三大沙箱吧。
第一種是快照沙箱。
它的原理是:把主應用的 window 物件做淺拷貝,將 window 的鍵值對存成一個 Hash Map。之後無論微應用對 window 做任何改動,當要在恢復環境時,把這個 Hash Map 又應用到 window 上就可以了。 大概如下圖所示。
稍微做下小結:
上面的 SnapshotSandbox 有一個問題:每次微應用 unmount 時都要對每個屬性值做一次 Diff,類似這樣:
for (const prop in window) { if (window[prop] !== this.windowSnapshot[prop]) { // 記錄微應用的變更 this.modifyPropsMap[prop] = window[prop]; // 恢復主應用的環境 window[prop] = this.windowSnapshot[prop]; } }
如果有 1000 個屬性就要對比 1000 次,不是那麼優雅。
LegacySandbox 的想法則是 通過監聽對 window 的修改來直接記錄 Diff 內容,因為只要對 window 屬性進行設定,那麼就會有兩種情況:
(當然這裡的變數名做了簡化)
通過 addedMap, prevMap 和 newMap 這三個變數就能反推出微應用以及原來環境的變化,qiankun 也能以此作為恢復環境的依據。
當然這裡的監聽用到了 ES6 的新語法 Proxy,不過這裡先不展開討論,在之後的系列文章上會會自己手動實現一個簡單的沙箱。
前面兩種沙箱都是 單例模式 下使用的沙箱。也即一個頁面中只能同時展示一個微應用,而且無論是 set 還是 get 依然是直接操作 window 物件。
在這樣單例模式下,當微應用修改全域性變數時依然會在原來的 window 上做修改,因此如果在同一個路由頁面下展示多個微應用時,依然會有環境變數汙染的問題。
為了避免真實的 window 被汙染,qiankun 實現了 ProxySandbox。它的想法是:
這樣一來連恢復環境都不需要了,因為每個微應用都有自己一個環境,當在 active 時就給這個微應用分配一個 fakeWindow,當 inactive 時就把這個 fakeWindow 存起來,以便之後再利用。
看完上面,你大概也知道了這些沙箱是怎麼恢復環境的 但是,回到我們的問題:qiankun 是怎麼把 a 和這些沙箱聯絡起來呢?也即寫下 window.a = 1 是怎麼做到對 a 變數隔離的呢?
這個邏輯的實現並不在 qiankun 的原始碼裡,而是在它所依賴的 import-html-entry 中,這裡做一下簡化:
const executableScript = ` ;(function(window, self, globalThis){ ;${scriptText}${sourceUrl} }).bind(window.proxy)(window.proxy, window.proxy, window.proxy); ` eval.call(window, executableScript)
把上面字串程式碼展開來看看:
function fn(window, self, globalThis) { // 你的 JavaScript code } const bindedFn = fn.bind(window.proxy); bindedFn(window.proxy, window.proxy, window.proxy);
可以發現這裡的程式碼做了三件事:
因此,當我們在 JS 檔案裡有 window.a = 1 時,實際上會變成:
function fn(window, self, globalThis) { window.a = 1; } const bindedFn = fn.bind(window.proxy); bindedFn(window.proxy, window.proxy, window.proxy);
那麼此時,window.a 的 window 就不是全域性 window 而是 fn 的入參 window 了。又因為我們把 window.proxy 作為入參傳入,所以 window.a 實際上為 window.proxy.a = 1。這也正好解釋了 qiankun 的 JS 隔離邏輯。
不知道看完上面的實現,你有沒有發現問題。
假如現在程式碼裡有隱式宣告或呼叫全域性物件的程式碼:
add = (a, b) => { return a + b } add(1, 2)
當這樣呼叫 add 時,上下文 this 則為剛剛繫結的 window.proxy。由於隱式宣告 add 不會自動掛載到 window.proxy 上,所以當執行 add,eval 就會報 add is undefined。詳見 這個 Issue。
不要覺得這種情況不會發生,實際上,這還是挺常見的:
我之前就遇到過這種情況:比如下面 Webpack 會注入腳手架定義好的 CDN 資源重試邏輯:
<script> var __JS_RETRY__ = {}; function __rpReport(data) { console.log('__rpReport'); } function __rpJsReport(loadType, msidType, url) { console.log('__rpJsReport'); } function __retryPlugin(event) { console.log('retryPlugin') } // 改成下面就可以了 // window.__JS_RETRY__ = {}; // // window.__rpReport = (data) => { // console.log('__rpReport'); // } // // window.__rpJsReport = (loadType, msidType, url) => { // console.log('__rpJsReport'); // } // // window.__retryPlugin = (event) => { // console.log('retryPlugin') // } </script>
這個問題的解決的方法也很簡單:
這樣一來,你就得每次打包程式碼以及釋出時執行一個指令碼來做這些文字替換,非常麻煩。而京東的新微應用框架 MicroApp 則提供了一套外掛系統:
它可以讓開發者在執行 JS 前去做程式碼文字的替換:
import microApp from '@micro-zoe/micro-app' microApp.start({ plugins: { // ... modules: { 'appName1': [{ loader(code, url, options) { if (url === 'xxx.js') { // 替換有問題的程式碼 code = code.replace('var abc =', 'window.abc =') } return code } }], } } })
如果要對接別的團隊的微應用時,而且正好他們有 a = 1 這樣的程式碼,那麼在載入微應用的時候直接修復全域性變數的問題,不需要通知他們修改,也不失為一種策略吧。
總結一下,qiankun 一共有 3 種沙箱:
要和這些沙箱結合起來使用,qiankun 會把要執行的 JS 包裹在立即執行函數中,通過繫結上下文和傳參的方式來改變 this 和 window 的值,讓它們指向 window.proxy 沙箱物件,最後再用 eval 來執行這個函數。
以上就是Qiankun原理詳解JS沙箱是如何做隔離的詳細內容,更多關於Qiankun原理JS沙箱隔離的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45