微前端-乾坤JS沙箱机制

59 阅读3分钟

乾坤

1.乾坤的 3 种 JS 隔离机制

  • 支持单个微应用的方案一:SnapshotSandBox
    • 遍历了 window,性能不好,同一时间只能激活一个微应用
  • 支持单个微应用的方案二:LegacySandBox
    • 优势:不需要遍历 window 上的所有属性
    • 劣势:同一时间只能激活一个微应用
  • 支持同时激活多个微应用的方案:ProxySandBox
    • 不需要遍历 window 上所有属性,性能良好,同一时间可以激活多个 微应用

LegacySandBox 其实也是代理方式,由于之后有了更好的方案 ProxySandBox ,支持多个微应用,因此会被逐步淘汰, 而 SnapshotSandBox 虽然遍历 window,性能不是很好,但是由于兼容性很好,因此还会存在很长的时间

SnapshotSandBox

/**
 * SnapshotSandBox
 * 快照沙箱,主要用于不支持proxy的浏览器(低版本浏览器)
 * 激活沙箱时候:将window信息存储到windowSnapshot中,如果modifyPropMap中有值,还需要
 * 还原上次的状态;激活期间,可能修改了window的数据,退出沙箱时将修改过的信息存储到modifyPropMap中
 * 并且把window还原
 * 
 *  new SnapshotSandbox() => 将window的快照信息存储到windowSnapshot中,并且从modifyPropMap中还原上次修改的值   =>   退出前:对windowSnapshot 和 当前window做diff比较,对于变更的信息存到modifyPropMap中,同时将windowSnapshot还原给window(还原进入时候的window)
 */
class SnapshotSandbox {
  windowSnapshot = {}
  modifyPropMap = {}

  // 微应用处于运行的状态 
  active() {
    // 1.保存window对象上所有属性的状态
    for (const prop in window) {
      this.windowSnapshot[prop] = window[prop]
    }
    //  2.恢复上一次在运行该微应用的时候所修改过的window上的属性
    Object.keys(this.modifyPropMap).forEach(prop => {
      window[prop] = this.modifyPropMap[prop]
    })
  }
  // 微应用即将退出状态 
  inactive() {
    for (const prop in window) {
      if (window[prop] !== this.windowSnapshot[prop]) {
        // 1.记录修改了window上的哪些属性 
        this.modifyPropMap[prop] = window[prop]
        // 2.将window上的属性状态还原至微应用运行之前的状态
        window[prop] = this.windowSnapshot[prop]
      }
    }

  }
}

window.city = 'Beijing'
const snapshotSandbox = new SnapshotSandbox()
console.log(window.city + '--------01')
snapshotSandbox.active()
window.city = 'Shanghai'
console.log(window.city + '--------02')
snapshotSandbox.inactive()
console.log(window.city + '--------03')
snapshotSandbox.active()
console.log(window.city + '--------04')

LegacySandBox

// LegacySandBox
// 单例代理盒子

class LegacySandBox {
  currentUpdateValueMap = new Map()
  modifedPropsOriginalValueMapSandBox = new Map()
  addedPropsMapInSandBox = new Map()
  proxyWindow = {}

  constructor() {
    const fakeWindow = Object.create(null)
    this.proxyWindow = new Proxy(fakeWindow, {
      set: (target, prop, value, receiver) => {
        const originalVal = window[prop]
        if (!window.hasOwnProperty(prop)) {
          this.addedPropsMapInSandBox.set(prop, value)
        } else if (!this.modifedPropsOriginalValueMapSandBox.has(prop)) {
          this.modifedPropsOriginalValueMapSandBox.set(prop, originalVal)
        }

        this.currentUpdateValueMap.set(prop, value)
        window[prop] = value
      },

      get: (target, prop, receiver) => {
        return window[prop]
      }
    })
  }

  setWindowProp(prop, value, isToDelete) {
    if (value === undefined && isToDelete) {
      delete window[prop]
    } else {
      window[prop] = value
    }
  }

  active() {
    // 恢复上一次微应用处于运行状态时候,对window上做的左右修改
    this.currentUpdateValueMap.forEach((value, prop) => {
      this.setWindowProp(prop, value)
    })
  }

  inactive() {
    // 还原window上原有的属性
    this.modifedPropsOriginalValueMapSandBox.forEach((value, prop) => {
      this.setWindowProp(prop, value)
    })
    // 删除在微应用运行期间window新增的属性
    this.addedPropsMapInSandBox.forEach((_, prop) => {
      this.setWindowProp(prop, undefined, true)
    })
  }
}


window.city = 'Beijing'
const legacySandBox = new LegacySandBox()
console.log(window.city + '--------01')
legacySandBox.active()
legacySandBox.proxyWindow.city = 'Shanghai'
console.log(window.city + '--------02')
legacySandBox.inactive()
console.log(window.city + '--------03')

ProxySandBox

// ProxySandBox
// 多例代理盒子
// 创建一个代理对象,当获取的属性并不在设置的空对象fakeWindow上则直接去window属性上找
// 而设置的时候则判断该微应用是否激活,如果激活则直接改fakeWindow,没有激活,直接不管该设置操作

class ProxySandBox {
  proxyWindow
  isRunning = false

  active() {
    this.isRunning = true
  }

  inactive() {
    this.isRunning = false
  }

  constructor() {
    const fakeWindow = Object.create(null)
    this.proxyWindow = new Proxy(fakeWindow, {
      set: (target, prop, value, receiver) => {
        if (this.isRunning) {
          target[prop] = value

        }
      },
      get: (target, prop, receiver) => {
        return prop in target ? target[prop] : window[prop]
      }
    })
  }
}


window.city = 'Beijing'
let proxySandBox1 = new ProxySandBox()
let proxySandBox2 = new ProxySandBox()

proxySandBox1.active()
proxySandBox2.active()

proxySandBox1.proxyWindow.city = 'Shanghai'
proxySandBox2.proxyWindow.city = 'Chengdu'

console.log('proxySandBox1----', proxySandBox1.proxyWindow.city);
console.log('proxySandBox2----', proxySandBox2.proxyWindow.city);
console.log('window.city----', window.city);
console.log('⬇️⬇️⬇️⬇️⬇️');
proxySandBox1.inactive()
proxySandBox2.inactive()

console.log('proxySandBox1----', proxySandBox1.proxyWindow.city);
console.log('proxySandBox2----', proxySandBox2.proxyWindow.city);
console.log('window.city-----', window.city);