欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Qiankun原理詳解JS沙箱是如何做隔離

 更新時(shí)間:2022年09月28日 17:05:44   作者:寫(xiě)代碼的海怪  
這篇文章主要為大家介紹了Qiankun原理詳解JS沙箱是如何做隔離示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

相信大家也知道 qiankun 有 SnapshotSandbox, LegacySandbox 和 ProxySandbox 這些沙箱,而它們又可以分為單例和多例兩種模式,網(wǎng)上也有很多文章對(duì)其進(jìn)行介紹。

但這些文章的關(guān)注點(diǎn)都是沙箱的環(huán)境恢復(fù)做的事,那 JS 的隔離到底是怎么做到的呢?

換個(gè)問(wèn)法,當(dāng)我寫(xiě) window.a = 1 的時(shí)候,a 是怎么被掛載到這些 XXXSandbox 上的呢?又或者我直接云修改 window.a = 123 時(shí),JS 沙箱到底是怎么隔離這個(gè) a 的呢?

總不能這樣吧:

window = window.sandbox
window.a = 1 // window.sandbox.a = 1

這篇文章就來(lái)簡(jiǎn)單聊聊 qiankun 沙箱那些事。

復(fù)習(xí)一下沙箱

這里我們還是稍微復(fù)習(xí)一下 qiankun 的三大沙箱吧。

SanpshotSandbox

第一種是快照沙箱。

它的原理是:把主應(yīng)用的 window 對(duì)象做淺拷貝,將 window 的鍵值對(duì)存成一個(gè) Hash Map。之后無(wú)論微應(yīng)用對(duì) window 做任何改動(dòng),當(dāng)要在恢復(fù)環(huán)境時(shí),把這個(gè) Hash Map 又應(yīng)用到 window 上就可以了。 大概如下圖所示。

稍微做下小結(jié):

  • 微應(yīng)用 mount 時(shí)
    • 先把上一次記錄的變更 modifyPropsMap 應(yīng)用到微應(yīng)用的全局 window,沒(méi)有則跳過(guò)
    • 淺復(fù)制主應(yīng)用的 window key-value 快照,用于下次恢復(fù)全局環(huán)境
  • 微應(yīng)用 unmount 時(shí)
    • 將當(dāng)前微應(yīng)用 window 的 key-value 和 快照 的 key-value 進(jìn)行 Diff,Diff 出來(lái)的結(jié)果用于下次恢復(fù)微應(yīng)用環(huán)境的依據(jù)
    • 將上次快照的 key-value 拷貝到主應(yīng)用的 window 上,以此恢復(fù)環(huán)境

LegacySandbox

上面的 SnapshotSandbox 有一個(gè)問(wèn)題:每次微應(yīng)用 unmount 時(shí)都要對(duì)每個(gè)屬性值做一次 Diff,類(lèi)似這樣:

for (const prop in window) {
  if (window[prop] !== this.windowSnapshot[prop]) {
    // 記錄微應(yīng)用的變更
    this.modifyPropsMap[prop] = window[prop];
    // 恢復(fù)主應(yīng)用的環(huán)境
    window[prop] = this.windowSnapshot[prop];
  }
}

如果有 1000 個(gè)屬性就要對(duì)比 1000 次,不是那么優(yōu)雅。

LegacySandbox 的想法則是 通過(guò)監(jiān)聽(tīng)對(duì) window 的修改來(lái)直接記錄 Diff 內(nèi)容,因?yàn)橹灰獙?duì) window 屬性進(jìn)行設(shè)置,那么就會(huì)有兩種情況:

  • 如果是新增屬性,那么存到 addedMap 里
  • 如果是更新屬性,那么把原來(lái)的鍵值存到 prevMap,把新的鍵值存到 newMap

(當(dāng)然這里的變量名做了簡(jiǎn)化)

通過(guò) addedMap, prevMap 和 newMap 這三個(gè)變量就能反推出微應(yīng)用以及原來(lái)環(huán)境的變化,qiankun 也能以此作為恢復(fù)環(huán)境的依據(jù)。

當(dāng)然這里的監(jiān)聽(tīng)用到了 ES6 的新語(yǔ)法 Proxy,不過(guò)這里先不展開(kāi)討論,在之后的系列文章上會(huì)會(huì)自己手動(dòng)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的沙箱。

ProxySandbox

前面兩種沙箱都是 單例模式 下使用的沙箱。也即一個(gè)頁(yè)面中只能同時(shí)展示一個(gè)微應(yīng)用,而且無(wú)論是 set 還是 get 依然是直接操作 window 對(duì)象。

在這樣單例模式下,當(dāng)微應(yīng)用修改全局變量時(shí)依然會(huì)在原來(lái)的 window 上做修改,因此如果在同一個(gè)路由頁(yè)面下展示多個(gè)微應(yīng)用時(shí),依然會(huì)有環(huán)境變量污染的問(wèn)題。

為了避免真實(shí)的 window 被污染,qiankun 實(shí)現(xiàn)了 ProxySandbox。它的想法是:

  • 把當(dāng)前 window 的一些原生屬性(如document, location等)拷貝出來(lái),單獨(dú)放在一個(gè)對(duì)象上,這個(gè)對(duì)象也稱(chēng)為 fakeWindow
  • 之后對(duì)每個(gè)微應(yīng)用分配一個(gè) fakeWindow
  • 當(dāng)微應(yīng)用修改全局變量時(shí):
    • 如果是原生屬性,則修改全局的 window
    • 如果是原生屬性,則修改 fakeWindow 里的內(nèi)容
  • 微應(yīng)用獲取全局變量時(shí):
    • 如果是原生屬性,則從 window 里拿
    • 如果不是原生屬性,則優(yōu)先從 fakeWindow 里獲取

這樣一來(lái)連恢復(fù)環(huán)境都不需要了,因?yàn)槊總€(gè)微應(yīng)用都有自己一個(gè)環(huán)境,當(dāng)在 active 時(shí)就給這個(gè)微應(yīng)用分配一個(gè) fakeWindow,當(dāng) inactive 時(shí)就把這個(gè) fakeWindow 存起來(lái),以便之后再利用。

隔離原理

看完上面,你大概也知道了這些沙箱是怎么恢復(fù)環(huán)境的 但是,回到我們的問(wèn)題:qiankun 是怎么把 a 和這些沙箱聯(lián)系起來(lái)呢?也即寫(xiě)下 window.a = 1 是怎么做到對(duì) a 變量隔離的呢?

這個(gè)邏輯的實(shí)現(xiàn)并不在 qiankun 的源碼里,而是在它所依賴(lài)的 import-html-entry 中,這里做一下簡(jiǎn)化:

const executableScript = `
  ;(function(window, self, globalThis){
    ;${scriptText}${sourceUrl}
  }).bind(window.proxy)(window.proxy, window.proxy, window.proxy);
`
eval.call(window, executableScript)

把上面字符串代碼展開(kāi)來(lái)看看:

function fn(window, self, globalThis) {
  // 你的 JavaScript code
}
const bindedFn = fn.bind(window.proxy);
bindedFn(window.proxy, window.proxy, window.proxy);

可以發(fā)現(xiàn)這里的代碼做了三件事:

  • 把要執(zhí)行 JS 代碼放在一個(gè)立即執(zhí)行函數(shù)中,且函數(shù)入?yún)⒂?window, self, globalThis
  • 給這個(gè)函數(shù) 綁定上下文 window.proxy
  • 執(zhí)行這個(gè)函數(shù),并 把上面提到的沙箱對(duì)象 window.proxy 作為入?yún)⒎謩e傳入

因此,當(dāng)我們?cè)?JS 文件里有 window.a = 1 時(shí),實(shí)際上會(huì)變成:

function fn(window, self, globalThis) {
  window.a = 1;
}
const bindedFn = fn.bind(window.proxy);
bindedFn(window.proxy, window.proxy, window.proxy);

那么此時(shí),window.a 的 window 就不是全局 window 而是 fn 的入?yún)?window 了。又因?yàn)槲覀儼?window.proxy 作為入?yún)魅?,所?window.a 實(shí)際上為 window.proxy.a = 1。這也正好解釋了 qiankun 的 JS 隔離邏輯。

XXX is undefined

不知道看完上面的實(shí)現(xiàn),你有沒(méi)有發(fā)現(xiàn)問(wèn)題。

假如現(xiàn)在代碼里有隱式聲明或調(diào)用全局對(duì)象的代碼:

add = (a, b) => {
  return a + b
}
add(1, 2)

當(dāng)這樣調(diào)用 add 時(shí),上下文 this 則為剛剛綁定的 window.proxy。由于隱式聲明 add 不會(huì)自動(dòng)掛載到 window.proxy 上,所以當(dāng)執(zhí)行 add,eval 就會(huì)報(bào) add is undefined。詳見(jiàn) 這個(gè) Issue。

不要覺(jué)得這種情況不會(huì)發(fā)生,實(shí)際上,這還是挺常見(jiàn)的:

  • 老舊的第三方 SDK JS 文件
  • Webpack 插件引入的 JS
  • 公司網(wǎng)關(guān)層自動(dòng)注入的 JS
  • 等等...

我之前就遇到過(guò)這種情況:比如下面 Webpack 會(huì)注入腳手架定義好的 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>

這個(gè)問(wèn)題的解決的方法也很簡(jiǎn)單:

  • 把代碼 a = 1 改成 window.a
  • 添加全局聲明 window a

這樣一來(lái),你就得每次打包代碼以及發(fā)布時(shí)執(zhí)行一個(gè)腳本來(lái)做這些文本替換,非常麻煩。而京東的新微應(yīng)用框架 MicroApp 則提供了一套插件系統(tǒng):

它可以讓開(kāi)發(fā)者在執(zhí)行 JS 前去做代碼文本的替換:

import microApp from '@micro-zoe/micro-app'
microApp.start({
  plugins: {
    // ...
    modules: {
      'appName1': [{
        loader(code, url, options) {
          if (url === 'xxx.js') {
            // 替換有問(wèn)題的代碼
            code = code.replace('var abc =', 'window.abc =')
          }
          return code
        }
      }],
    }
  }
})

如果要對(duì)接別的團(tuán)隊(duì)的微應(yīng)用時(shí),而且正好他們有 a = 1 這樣的代碼,那么在加載微應(yīng)用的時(shí)候直接修復(fù)全局變量的問(wèn)題,不需要通知他們修改,也不失為一種策略吧。

總結(jié)

總結(jié)一下,qiankun 一共有 3 種沙箱:

  • SnapshotSandbox:記錄 window 對(duì)象,每次 unmount 都要和微應(yīng)用的環(huán)境進(jìn)行 Diff
  • LegacySandbox:在微應(yīng)用修改 window.xxx 時(shí)直接記錄 Diff,將其用于環(huán)境恢復(fù)
  • ProxySandbox:為每個(gè)微應(yīng)用分配一個(gè) fakeWindow,當(dāng)微應(yīng)用操作 window 時(shí),其實(shí)是在 fakeWindow 上操作

要和這些沙箱結(jié)合起來(lái)使用,qiankun 會(huì)把要執(zhí)行的 JS 包裹在立即執(zhí)行函數(shù)中,通過(guò)綁定上下文和傳參的方式來(lái)改變 this 和 window 的值,讓它們指向 window.proxy 沙箱對(duì)象,最后再用 eval 來(lái)執(zhí)行這個(gè)函數(shù)。

以上就是Qiankun原理詳解JS沙箱是如何做隔離的詳細(xì)內(nèi)容,更多關(guān)于Qiankun原理JS沙箱隔離的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 微信小程序 MD5加密登錄密碼詳解及實(shí)例代碼

    微信小程序 MD5加密登錄密碼詳解及實(shí)例代碼

    這篇文章主要介紹了微信小程序 MD5加密登錄密碼詳解及實(shí)例代碼的相關(guān)資料,這里附有實(shí)例代碼,需要的朋友可以參考下
    2017-01-01
  • JS代碼檢查工具ESLint介紹與使用方法

    JS代碼檢查工具ESLint介紹與使用方法

    ESLint是一個(gè)JavaScript代碼靜態(tài)檢查工具,可以檢查JavaScript的語(yǔ)法錯(cuò)誤,提示潛在的bug,本文將詳細(xì)介紹ESLint的使用方法
    2020-02-02
  • JQ中$(window).load和$(document).ready區(qū)別與執(zhí)行順序

    JQ中$(window).load和$(document).ready區(qū)別與執(zhí)行順序

    JQ中的$(document).ready()大家應(yīng)該用的非常多,基本每個(gè)JS腳本中都有這個(gè)函數(shù)的出現(xiàn)有時(shí)甚至?xí)霈F(xiàn)多個(gè),那么另一個(gè)加載函數(shù)$(window).load相對(duì)出現(xiàn)的次數(shù)就很少了,下面為大家介紹一下兩者的區(qū)別與他們的執(zhí)行順序
    2017-03-03
  • Selection與Range 對(duì)象操作示例指南

    Selection與Range 對(duì)象操作示例指南

    這篇文章主要為大家介紹了Selection與Range 對(duì)象操作示例指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • 微信小程序 122100版本更新問(wèn)題解決方案

    微信小程序 122100版本更新問(wèn)題解決方案

    這篇文章主要介紹了微信小程序 122100版本更新問(wèn)題解決方案的相關(guān)資料,這里對(duì)微信小程序版本更新該如何解決提供解決方案,需要的朋友可以參考下
    2016-12-12
  • JS跨域(Access-Control-Allow-Origin)前后端解決方案詳解

    JS跨域(Access-Control-Allow-Origin)前后端解決方案詳解

    這篇文章主要介紹了瀏覽器跨域(Access-Control-Allow-Origin)解決方案詳解包括了前端跨域,后端跨域,js原生實(shí)現(xiàn)jsonp,jQuery實(shí)現(xiàn)jsonp,vue.js實(shí)現(xiàn)jsonp,需要的朋友可以參考下
    2022-01-01
  • 微信小程序(二十)slider組件詳細(xì)介紹

    微信小程序(二十)slider組件詳細(xì)介紹

    這篇文章主要介紹了 微信小程序(二十)slider組件詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下
    2016-09-09
  • 分享5個(gè)JS?高階函數(shù)

    分享5個(gè)JS?高階函數(shù)

    這篇文章主要給大家分享了5個(gè)JS高階函數(shù),在JavaScript中,函數(shù)實(shí)際上也是一個(gè)數(shù)據(jù),也就是說(shuō)函數(shù)也可以賦值給一個(gè)變量。本篇文章就來(lái)介紹一些JavaScript中的高階函數(shù)的用法,具有一定的參考價(jià)值,需要的朋友可以參考一下
    2021-12-12
  • ResizeObserver?API使用示例詳解

    ResizeObserver?API使用示例詳解

    這篇文章主要為大家介紹了ResizeObserver?API使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 自定義range?sliders滑塊實(shí)現(xiàn)元素拖動(dòng)方法

    自定義range?sliders滑塊實(shí)現(xiàn)元素拖動(dòng)方法

    這篇文章主要為大家介紹了自定義range?sliders滑塊實(shí)現(xiàn)元素拖動(dòng)方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08

最新評(píng)論