• 乾坤js隔离


    乾坤,作为一款微前端领域的知名框架,其建立在single-spa基础上。相较于single-spa,乾坤做了两件重要的事情,其一是加载资源,第二是进行资源隔离。而资源隔离又分为Js资源隔离和css资源隔离。

    我们把Js隔离机制常常称作沙箱,事实上,乾坤有三种Js隔离机制,并且在源代码中也是以 SnapshotSandbox、LegacySandbox、ProxySandbox三个类名来指代三种不同的隔离机制。下文我们统一以快照沙箱、支持单应用的代理沙箱、支持多应用的代理沙箱,来代表这三种不同的Js隔离机制。

    1、快照沙箱SnapshotSandbox:有个缺点,就是需要遍历window上的所有属性,性能较差。
    2、LegacySandbox,可以实现和快照沙箱一样的功能,但是却性能更好,和SnapshotSandbox一样,由于会污染全局的window,LegacySandbox也仅仅允许页面同时运行一个微应用,所以我们也称LegacySandbox为支持单应用的代理沙箱。
    3、ProxySandbox,可以支持一个页面运行多个微应用,因此我们称ProxySandbox为支持多应用的代理沙箱。
    LegacySandbox在未来应该会消失,因为LegacySandbox可以做的事情,ProxySandbox都可以做,而SanpsshotSandbox因为向下兼容的原因反而会和ProxySandbox长期并存。下面我们就编码实现这三种沙箱机制的核心逻辑。

    1、快照沙箱-极简版

    1. class SnapshotSandBox{
    2. windowSnapshot = {};
    3. modifyPropsMap = {};
    4. active(){
    5. for(const prop in window){
    6. this.windowSnapshot[prop] = window[prop];
    7. }
    8. Object.keys(this.modifyPropsMap).forEach(prop=>{
    9. window[prop] = this.modifyPropsMap[prop];
    10. });
    11. }
    12. inactive(){
    13. for(const prop in window){
    14. if(window[prop] !== this.windowSnapshot[prop]){
    15. this.modifyPropsMap[prop] = window[prop];
    16. window[prop] = this.windowSnapshot[prop];
    17. }
    18. }
    19. }
    20. }
    21. // 验证:
    22. let snapshotSandBox = new SnapshotSandBox();
    23. snapshotSandBox.active();
    24. window.city = 'Beijing';
    25. console.log("window.city-01:", window.city);
    26. snapshotSandBox.inactive();
    27. console.log("window.city-02:", window.city);
    28. snapshotSandBox.active();
    29. console.log("window.city-03:", window.city);
    30. snapshotSandBox.inactive();
    31. //输出:
    32. //window.city-01: Beijing
    33. //window.city-02: undefined
    34. //window.city-03: Beijing

    从上面的代码可以看出,快照沙箱的核心逻辑很简单,就是在激活沙箱和沙箱失活的时候各做两件事情。

    注:沙箱激活 就是此时我们的微应用处于运行中,这个阶段有可能对window上的属性进行操作改变;沙箱失活 就是此时我们的微应用已经停止了对window的影响

    在沙箱激活的时候:

    • 记录window当时的状态(我们把这个状态称之为快照,也就是快照沙箱这个名称的来源);

    • 恢复上一次沙箱失活时记录的沙箱运行过程中对window做的状态改变,也就是上一次沙箱激活后对window做了哪些改变,现在也保持一样的改变。

    在沙箱失活的时候:

    • 记录window上有哪些状态发生了变化(沙箱自激活开始,到失活的这段时间);

    • 清除沙箱在激活之后在window上改变的状态,从代码可以看出,就是让window此时的属性状态和刚激活时候的window的属性状态进行对比,不同的属性状态就以快照为准,恢复到未改变之前的状态。

    从上面可以看出,快照沙箱存在两个重要的问题:

    • 会改变全局window的属性,如果同时运行多个微应用,多个应用同时改写window上的属性,势必会出现状态混乱,这也就是为什么快照沙箱无法支持多各微应用同时运行的原因。关于这个问题,下文中支持多应用的代理沙箱可以很好的解决这个问题;

    • 会通过for(prop in window){}的方式来遍历window上的所有属性,window属性众多,这其实是一件很耗费性能的事情。关于这个问题支持单应用的代理沙箱支持多应用的代理沙箱都可以规避。

    2、支持单应用的代理沙箱-极简版

    1. class LegacySandBox{
    2. addedPropsMapInSandbox = new Map();
    3. modifiedPropsOriginalValueMapInSandbox = new Map();
    4. currentUpdatedPropsValueMap = new Map();
    5. proxyWindow;
    6. setWindowProp(prop, value, toDelete = false){
    7. if(value === undefined && toDelete){
    8. delete window[prop];
    9. }else{
    10. window[prop] = value;
    11. }
    12. }
    13. active(){
    14. this.currentUpdatedPropsValueMap.forEach((value, prop)=>this.setWindowProp(prop, value));
    15. }
    16. inactive(){
    17. this.modifiedPropsOriginalValueMapInSandbox.forEach((value, prop)=>this.setWindowProp(prop, value));
    18. this.addedPropsMapInSandbox.forEach((_, prop)=>this.setWindowProp(prop, undefined, true));
    19. }
    20. constructor(){
    21. const fakeWindow = Object.create(null);
    22. this.proxyWindow = new Proxy(fakeWindow,{
    23. set:(target, prop, value, receiver)=>{
    24. const originalVal = window[prop];
    25. if(!window.hasOwnProperty(prop)){
    26. this.addedPropsMapInSandbox.set(prop, value);
    27. }else if(!this.modifiedPropsOriginalValueMapInSandbox.has(prop)){
    28. this.modifiedPropsOriginalValueMapInSandbox.set(prop, originalVal);
    29. }
    30. this.currentUpdatedPropsValueMap.set(prop, value);
    31. window[prop] = value;
    32. },
    33. get:(target, prop, receiver)=>{
    34. return target[prop];
    35. }
    36. });
    37. }
    38. }
    39. // 验证:
    40. let legacySandBox = new LegacySandBox();
    41. legacySandBox.active();
    42. legacySandBox.proxyWindow.city = 'Beijing';
    43. console.log('window.city-01:', window.city);
    44. legacySandBox.inactive();
    45. console.log('window.city-02:', window.city);
    46. legacySandBox.active();
    47. console.log('window.city-03:', window.city);
    48. legacySandBox.inactive();
    49. // 输出:
    50. // window.city-01: Beijing
    51. // window.city-02: undefined
    52. // window.city-03: Beijing

    从上面的代码可以看出,其实现的功能和快照沙箱是一模一样的,不同的是,通过三个变量来记住沙箱激活后window发生变化过的所有属性,这样在后续的状态还原时候就不再需要遍历window的所有属性来进行对比,提升了程序运行的性能。但是这仍然改变不了这种机制仍然污染了window的状态的事实,因此也就无法承担起同时支持多个微应用运行的任务。

    3、支持多应用的代理沙箱-极简版

    1. class ProxySandBox{
    2. proxyWindow;
    3. isRunning = false;
    4. active(){
    5. this.isRunning = true;
    6. }
    7. inactive(){
    8. this.isRunning = false;
    9. }
    10. constructor(){
    11. const fakeWindow = Object.create(null);
    12. this.proxyWindow = new Proxy(fakeWindow,{
    13. set:(target, prop, value, receiver)=>{
    14. if(this.isRunning){
    15. target[prop] = value;
    16. }
    17. },
    18. get:(target, prop, receiver)=>{
    19. return prop in target ? target[prop] : window[prop];
    20. }
    21. });
    22. }
    23. }
    24. // 验证:
    25. let proxySandBox1 = new ProxySandBox();
    26. let proxySandBox2 = new ProxySandBox();
    27. proxySandBox1.active();
    28. proxySandBox2.active();
    29. proxySandBox1.proxyWindow.city = 'Beijing';
    30. proxySandBox2.proxyWindow.city = 'Shanghai';
    31. console.log('active:proxySandBox1:window.city:', proxySandBox1.proxyWindow.city);
    32. console.log('active:proxySandBox2:window.city:', proxySandBox2.proxyWindow.city);
    33. console.log('window:window.city:', window.city);
    34. proxySandBox1.inactive();
    35. proxySandBox2.inactive();
    36. console.log('inactive:proxySandBox1:window.city:', proxySandBox1.proxyWindow.city);
    37. console.log('inactive:proxySandBox2:window.city:', proxySandBox2.proxyWindow.city);
    38. console.log('window:window.city:', window.city);
    39. // 输出:
    40. // active:proxySandBox1:window.city: Beijing
    41. // active:proxySandBox2:window.city: Shanghai
    42. // window:window.city: undefined
    43. // inactive:proxySandBox1:window.city: Beijing
    44. // inactive:proxySandBox2:window.city: Shanghai
    45. // window:window.city: undefined

    从上面的代码可以发现,ProxySandbox,完全不存在状态恢复的逻辑,同时也不需要记录属性值的变化,因为所有的变化都是沙箱内部的变化,和window没有关系,window上的属性至始至终都没有受到过影响。我们可能会问,ProxySandbox已经这么好了,性能优良还支持多个微应用同时运行,那自然也支持单个微应用运行,那LegacySandbox存在还有什么意义呢,这个问题问得很好,其实本身在未来存在的意义也不大了,只不过是因为历史原因还在服役罢了,从Legacy这个单词就已经能推断出LegacySandbox在乾坤中的位置。我们可能还会继续问,那SnapshotSandbox存在还有什么意义呢,这个还真有不小作用,Proxy是新ES6的新事物,低版本浏览器无法兼容所以SnapshotSandbox还会长期存在。虽然这里极简版本逻辑很少,但是由于ProxySandbox要支持多个微应用运行,里面的逻辑会SnapshotsSandbox、LegacySandbox的都要丰富一些。

    其实到了这里,如果读者朋友已经理解了上面的思路,就可以说已经理解了乾坤的Js隔离机制。

  • 相关阅读:
    C练题笔记之:Leetcode-827. 最大人工岛
    【JDBC】数据库连接池:德鲁伊druid的使用
    微信小程序独立分包与分包预下载
    八大排序——快速排序
    人工智能的前世今生与未来
    【Python】read() || readline() || readlines()-笔记
    死锁详解
    41_引用类型用法总结
    Redis 3.2.3 crashed by signal: 11 服务宕机问题排查
    Mac 环境安装 Tomcat
  • 原文地址:https://blog.csdn.net/sunnyjingqi/article/details/134001781