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

微前端qiankun沙箱實現(xiàn)源碼解讀

 更新時間:2022年06月08日 15:44:36   作者:杰出D  
這篇文章主要為大家介紹了微前端qiankun沙箱實現(xiàn)的源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

上篇我們介紹了微前端實現(xiàn)沙箱的幾種方式,沒看過的可以下看下JS沙箱這篇內(nèi)容,掃盲一下。接下來我們通過源 碼詳細分析下qiankun沙箱實現(xiàn),我們clone下qiankun代碼,代碼主要在sandbox文件夾下,目錄結(jié)構(gòu)為

├── common.ts
├── index.ts             // 入口文件
├── legacy
│   └── sandbox.ts       // 代理沙箱(單實例)
├── patchers             // 該暫時不用關(guān)心,主要是給沙箱打補丁增強沙箱能力
│   ├── __tests__
│   ├── css.ts
│   ├── dynamicAppend
│   ├── historyListener.ts
│   ├── index.ts
│   ├── interval.ts
│   └── windowListener.ts
├── proxySandbox.ts       // 代理沙箱(多實例)
└── snapshotSandbox.ts    //快照沙箱

我們主要關(guān)注 proxySandbox.ts, snapshotSandbox.ts 文件和 legacy 文件夾。patchers 文件夾的內(nèi)容主要為了給我們實例的沙箱打補丁,增強沙箱的一些能力先不用關(guān)注。

從上面分析我們可看出 qiankun JS沙箱主要有snapshotSandbox快照沙箱,legacySandbox單實例代理沙箱,proxySandbox多實例代理沙箱。

我們從入口文件index.ts可以看到創(chuàng)建沙箱的代碼

  let sandbox: SandBox;
  if (window.Proxy) {
    sandbox = useLooseSandbox ? new LegacySandbox(appName) : new ProxySandbox(appName);
  } else {
    sandbox = new SnapshotSandbox(appName);
  }

我們可以看出如果瀏覽器支持Proxy就用LegacySandbox或ProxySandbox沙箱,比較老的瀏覽器用SnapshotSandbox沙箱,現(xiàn)在在支持proxy的瀏覽器qiankun里主要用ProxySandbox。

下面各種沙箱我們具體分析一下

LegacySandbox單實例沙箱

/**
 * 判斷該屬性也能從對應(yīng)的對象上被刪除
 */
function isPropConfigurable(target: typeof window, prop: PropertyKey) {
  const descriptor = Object.getOwnPropertyDescriptor(target, prop);
  return descriptor ? descriptor.configurable : true;
}
/**
 * 設(shè)置window屬性
 * @param prop
 * @param value
 * @param toDelete 是否是刪除屬性
 */
function setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) {
  if (value === undefined && toDelete) {
    delete (window as any)[prop];
  } else if (isPropConfigurable(window, prop) && typeof prop !== 'symbol') {
    Object.defineProperty(window, prop, { writable: true, configurable: true });
    (window as any)[prop] = value;
  }
}
/**
 * 基于 Proxy 實現(xiàn)的沙箱
 * TODO: 為了兼容性 singular 模式下依舊使用該沙箱,等新沙箱穩(wěn)定之后再切換
 */
export default class SingularProxySandbox implements SandBox {
  /** 沙箱期間新增的全局變量 */
  private addedPropsMapInSandbox = new Map<PropertyKey, any>();
  /** 沙箱期間更新的全局變量 */
  private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any>();
  /** 持續(xù)記錄更新的(新增和修改的)全局變量的 map,用于在任意時刻做 snapshot */
  private currentUpdatedPropsValueMap = new Map<PropertyKey, any>();
  name: string; // 名稱
  proxy: WindowProxy; // 初始化代理對象
  type: SandBoxType; // 沙箱類型
  sandboxRunning = true; // 沙箱是否在運行
  latestSetProp: PropertyKey | null = null; // 最后設(shè)置的props
  /**
   * 激活沙箱的方法
   */
  active() {
    if (!this.sandboxRunning) {
      // 之前記錄新增和修改的全局變量更新到當前window上。
      this.currentUpdatedPropsValueMap.forEach((v, p) => setWindowProp(p, v));
    }
    this.sandboxRunning = true; // 設(shè)置沙箱在運行
  }
  /**
   * 失活沙箱的方法
   */
  inactive() {
    // 失活沙箱把記錄的初始值還原回去
    this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) => setWindowProp(p, v));
    // 沙箱失活的時候把新增的屬性從window上給刪除
    this.addedPropsMapInSandbox.forEach((_, p) => setWindowProp(p, undefined, true));
    this.sandboxRunning = false; // 設(shè)置沙箱不在運行
  }
  constructor(name: string) {
    this.name = name;
    this.type = SandBoxType.LegacyProxy;
    const { addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap } = this;
    const rawWindow = window; // 獲取當前window對象
    const fakeWindow = Object.create(null) as Window; // 創(chuàng)建一個代理對象的window對象
    const proxy = new Proxy(fakeWindow, {
      set: (_: Window, p: PropertyKey, value: any): boolean => {
        if (this.sandboxRunning) { // 判斷沙箱是否在啟動
          if (!rawWindow.hasOwnProperty(p)) {
            // 當前window上沒有該屬性,在addedPropsMapInSandbox上記錄添加的屬性
            addedPropsMapInSandbox.set(p, value);
          } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
            // 如果當前 window 對象存在該屬性,且 record map 中未記錄過,則記錄該屬性初始值
            const originalValue = (rawWindow as any)[p];
            modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
          }
          // 記錄新增和修改的屬性
          currentUpdatedPropsValueMap.set(p, value);
          // 必須重新設(shè)置 window 對象保證下次 get 時能拿到已更新的數(shù)據(jù)
          (rawWindow as any)[p] = value;
          // 更新下最后設(shè)置的props
          this.latestSetProp = p;
          return true;
        }
        // 在 strict-mode 下,Proxy 的 handler.set 返回 false 會拋出 TypeError,在沙箱卸載的情況下應(yīng)該忽略錯誤
        return true;
      },
      get(_: Window, p: PropertyKey): any {
        // 判斷用window.top, window.parent等也返回代理對象,在ifream環(huán)境也會返回代理對象。做到了真正的隔離,
        if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') {
          return proxy;
        }
        const value = (rawWindow as any)[p];
        return getTargetValue(rawWindow, value); // 返回當前值
      },
      /**
       * 用 in 操作判斷屬性是否存在的時候去window上判斷,而不是在代理對象上判斷
       */
      has(_: Window, p: string | number | symbol): boolean {
        return p in rawWindow;
      },
      /**
       * 獲取對象屬性描述的時候也是從window上去判斷,代理對象上可能沒有
       */
      getOwnPropertyDescriptor(_: Window, p: PropertyKey): PropertyDescriptor | undefined {
        const descriptor = Object.getOwnPropertyDescriptor(rawWindow, p);
        if (descriptor && !descriptor.configurable) {
          descriptor.configurable = true;
        }
        return descriptor;
      },
    });
    this.proxy = proxy;
  }
}

上面代碼都有注釋,整個思路主要還是操作window對象,通過激活沙箱時還原子應(yīng)用的狀態(tài),卸載時還原主應(yīng)用的狀態(tài)來實現(xiàn)沙箱隔離的。跟我們上篇文章的簡單實現(xiàn)不同點qiankun做了兼容,在健壯性和嚴謹性都比較好。

接下來,我們重點看下現(xiàn)役的ProxySandbox沙箱

ProxySandbox多實例沙箱

我們先看創(chuàng)建fakeWindow的方法,這里很巧妙,主要是把window上不支持改變和刪除的屬性,但有g(shù)et方法的屬性創(chuàng)建到fakeWindow上。這里有幾個我們平常在業(yè)務(wù)開發(fā)用的不多的幾個API,主要是Object.getOwnPropertyDescriptor和Object.defineProperty。具體詳細細節(jié),可以參考Object static function

/**
 * 創(chuàng)建一個FakeWindow, 把window上不支持改變和刪除的屬性創(chuàng)建到我們創(chuàng)建的fake window上
 * @param global 
 * @returns 
 */
function createFakeWindow(global: Window) {
  const propertiesWithGetter = new Map<PropertyKey, boolean>();
  const fakeWindow = {} as FakeWindow;
  Object.getOwnPropertyNames(global)
    // 篩選出不可以改變或者可以刪除的屬性
    .filter((p) => {
      const descriptor = Object.getOwnPropertyDescriptor(global, p);
      return !descriptor?.configurable;
    })
    // 重新定義這些屬性可以可以改變和刪除
    .forEach((p) => {
      const descriptor = Object.getOwnPropertyDescriptor(global, p);
      if (descriptor) {
        // 判斷有g(shù)et屬性,說明可以獲取該屬性值
        const hasGetter = Object.prototype.hasOwnProperty.call(descriptor, 'get');
        if (
          p === 'top' ||
          p === 'parent' ||
          p === 'self' ||
          p === 'window'
        ) {
          descriptor.configurable = true;
          if (!hasGetter) {
            descriptor.writable = true;
          }
        }
        if (hasGetter) propertiesWithGetter.set(p, true);
        rawObjectDefineProperty(fakeWindow, p, Object.freeze(descriptor));
      }
    });
  return {
    fakeWindow,
    propertiesWithGetter, // 記錄有g(shù)et方法的屬性
  };
}

前期工作已準備好,接下來我們看沙箱的主要代碼

// 全局變量,記錄沙箱激活的數(shù)量
let activeSandboxCount = 0;
/**
 * 基于 Proxy 實現(xiàn)的沙箱
 */
export default class ProxySandbox implements SandBox {
  /** window 值變更記錄 */
  private updatedValueSet = new Set<PropertyKey>();
  name: string; // 名稱
  proxy: WindowProxy; // 初始化代理對象
  type: SandBoxType; // 沙箱類型
  sandboxRunning = true; // 沙箱是否在運行
  latestSetProp: PropertyKey | null = null; // 最后設(shè)置的props
  active() {
    // 沙箱激活記,記錄激活數(shù)量
    if (!this.sandboxRunning) activeSandboxCount++;
    this.sandboxRunning = true;
  }
  inactive() {
    // 失活沙箱,減去激活數(shù)量
    if (--activeSandboxCount === 0) {
      // 在白名單的屬性要從window上刪除
      variableWhiteList.forEach((p) => {
        if (this.proxy.hasOwnProperty(p)) {
          delete window[p];
        }
      });
    }
    this.sandboxRunning = false;
  }
  constructor(name: string) {
    this.name = name;
    this.type = SandBoxType.Proxy;
    const { updatedValueSet } = this;
    const rawWindow = window;
    // 通過createFakeWindow創(chuàng)建一個fakeWindow對象
    const { fakeWindow, propertiesWithGetter } = createFakeWindow(rawWindow);
    const descriptorTargetMap = new Map<PropertyKey, SymbolTarget>();
    const hasOwnProperty = (key: PropertyKey) => fakeWindow.hasOwnProperty(key) || rawWindow.hasOwnProperty(key);
    // 代理 fakeWindow
    const proxy = new Proxy(fakeWindow, {
      set: (target: FakeWindow, p: PropertyKey, value: any): boolean => {
        if (this.sandboxRunning) {
          // 判斷window上有該屬性,并獲取到屬性的 writable, configurable, enumerable等值。
          if (!target.hasOwnProperty(p) && rawWindow.hasOwnProperty(p)) {
            const descriptor = Object.getOwnPropertyDescriptor(rawWindow, p);
            const { writable, configurable, enumerable } = descriptor!;
            if (writable) {
              // 通過defineProperty把值復(fù)制到代理對象上,
              Object.defineProperty(target, p, {
                configurable,
                enumerable,
                writable,
                value,
              });
            }
          } else {
            // window上沒有屬性,支持設(shè)置值
            target[p] = value;
          }
          // 存放一些變量的白名單
          if (variableWhiteList.indexOf(p) !== -1) {
            // @ts-ignore
            rawWindow[p] = value;
          }
          // 記錄變更記錄
          updatedValueSet.add(p);
          this.latestSetProp = p;
          return true;
        }
        // 在 strict-mode 下,Proxy 的 handler.set 返回 false 會拋出 TypeError,在沙箱卸載的情況下應(yīng)該忽略錯誤
        return true;
      },
      get(target: FakeWindow, p: PropertyKey): any {
        if (p === Symbol.unscopables) return unscopables;
        // 判斷用window.top, window.parent等也返回代理對象,在ifream環(huán)境也會返回代理對象。做到了真正的隔離,
        if (p === 'window' || p === 'self') {
          return proxy;
        }
        if (p === 'globalThis') {
          return proxy;
        }
        if (
          p === 'top' ||
          p === 'parent'
        ) {
          if (rawWindow === rawWindow.parent) {
            return proxy;
          }
          return (rawWindow as any)[p];
        }
        // hasOwnProperty的值表示為rawWindow.hasOwnProperty
        if (p === 'hasOwnProperty') {
          return hasOwnProperty;
        }
        // 如果獲取document和eval對象就直接返回,相當月共享一些全局變量
        if (p === 'document' || p === 'eval') {
          setCurrentRunningSandboxProxy(proxy);
          nextTick(() => setCurrentRunningSandboxProxy(null));
          switch (p) {
            case 'document':
              return document;
            case 'eval':
              return eval;
          }
        }
        // 返回當前值
        const value = propertiesWithGetter.has(p)
          ? (rawWindow as any)[p]
          : p in target
          ? (target as any)[p]
          : (rawWindow as any)[p];
        return getTargetValue(rawWindow, value);
      },
      /**
       * 以下這些方法都是在對象的處理上做了很多的兼容,保證沙箱的健壯性和完整性
       */
      has(target: FakeWindow, p: string | number | symbol): boolean {
      },
      getOwnPropertyDescriptor ....
      this.proxy = proxy;
      activeSandboxCount++;
  }
}

整體我們可以看到先創(chuàng)建fakeWindow對象,然后對這個對象進行代理,ProxySandbox不會操作window上的實例,會使用fakeWindow上的屬性,從而實現(xiàn)多實例。

實現(xiàn)代理的過程中還對 as、ownKeys、getOwnPropertyDescriptor、defineProperty、deleteProperty做了重新定義,會保證沙箱的健壯性和完整性。

跟我們上篇文章有點不一樣的就是共享對象,qiankun直接寫死了,只有doucument和eval是共享的。

最后我們來看下snapshotSandbox沙箱,相對比較簡單

SapshotSandbox 快照沙箱

/**
 * 基于 diff 方式實現(xiàn)的沙箱,用于不支持 Proxy 的低版本瀏覽器
 */
export default class SnapshotSandbox implements SandBox {
  name: string; // 名稱
  proxy: WindowProxy; // 初始化代理對象
  type: SandBoxType; // 沙箱類型
  sandboxRunning = true; // 沙箱是否在運行
  private windowSnapshot!: Window; // 當前快照
  private modifyPropsMap: Record<any, any> = {}; // 記錄修改的屬性
  constructor(name: string) {
    this.name = name;
    this.proxy = window;
    this.type = SandBoxType.Snapshot;
  }
  active() {
    // 記錄當前快照
    this.windowSnapshot = {} as Window;
    iter(window, (prop) => {
      this.windowSnapshot[prop] = window[prop];
    });
    // 恢復(fù)之前的變更
    Object.keys(this.modifyPropsMap).forEach((p: any) => {
      window[p] = this.modifyPropsMap[p];
    });
    this.sandboxRunning = true;
  }
  inactive() {
    this.modifyPropsMap = {};
    iter(window, (prop) => {
      if (window[prop] !== this.windowSnapshot[prop]) {
        // 記錄變更,恢復(fù)環(huán)境
        this.modifyPropsMap[prop] = window[prop];
        window[prop] = this.windowSnapshot[prop];
      }
    });
    this.sandboxRunning = false;
  }
}

快照沙箱比較簡單,激活的時候?qū)ψ兏膶傩宰鲂┯涗?,失活的時候移除這些記錄,還有運行期間所有的屬性都報存在window上,所有只能是單實例。

結(jié)束語

參考

以上就是JS沙箱,qiankun實現(xiàn)的比較完善,各種情況基本都考慮到了。下篇我們說一下css常見的隔離方案,更多關(guān)于微前端qiankun沙箱的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JavaScript變量類型以及變量之間的轉(zhuǎn)換你了解嗎

    JavaScript變量類型以及變量之間的轉(zhuǎn)換你了解嗎

    這篇文章主要為大家詳細介紹了JavaScript變量類型以及變量之間的轉(zhuǎn)換,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • 微信小程序如何獲取地址

    微信小程序如何獲取地址

    這篇文章主要介紹了微信小程序獲取地址的具體方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • JavaScript預(yù)解析及相關(guān)技巧分析

    JavaScript預(yù)解析及相關(guān)技巧分析

    這篇文章主要介紹了JavaScript預(yù)解析及相關(guān)技巧,結(jié)合實例形式分析了JavaScript與解析的原理,步驟與相關(guān)技巧,需要的朋友可以參考下
    2016-04-04
  • 用js制作淘寶放大鏡效果

    用js制作淘寶放大鏡效果

    這篇文章主要為大家詳細介紹了js制作淘寶放大鏡效果的相關(guān)資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • javascript面向?qū)ο罂焖偃腴T實例

    javascript面向?qū)ο罂焖偃腴T實例

    這篇文章主要介紹了javascript面向?qū)ο罂焖偃腴T實例,以一個簡單實例分析了javascript面向?qū)ο蟮闹袑ο蟮亩x與使用技巧,需要的朋友可以參考下
    2015-01-01
  • 微信小程序通過uni-app進行全局分享

    微信小程序通過uni-app進行全局分享

    這篇文章主要介紹了微信小程序通過uni-app進行全局分享,小編覺得挺不錯的,現(xiàn)在分享給大家,需要的朋友可以收藏下
    2021-11-11
  • bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例

    bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例

    今天小編就為大家分享一篇bootstrap select2插件用ajax來獲取和顯示數(shù)據(jù)的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • javascript二維數(shù)組轉(zhuǎn)置實例

    javascript二維數(shù)組轉(zhuǎn)置實例

    這篇文章主要介紹了javascript二維數(shù)組轉(zhuǎn)置方法,實例分析了數(shù)組行列交換的轉(zhuǎn)置技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-01-01
  • javascript中l(wèi)ayim之查找好友查找群組

    javascript中l(wèi)ayim之查找好友查找群組

    這篇文章主要介紹了javascript中l(wèi)ayim之查找好友查找群組,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • node在兩個div之間移動,用ztree實現(xiàn)

    node在兩個div之間移動,用ztree實現(xiàn)

    本文介紹了“node在兩個div之間移動,用ztree實現(xiàn)”的方法,需要的朋友可以參考一下
    2013-03-03

最新評論