引言

沙箱,即sandbox。
就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。

比如浏览器的每个tab页,就是一个沙箱。
渲染进程被沙箱(Sandbox)隔离,网页 web 代码内容必须通过 IPC 通道才能与浏览器内核进程通信,通信过程会进行安全的检查。沙箱设计的目的是为了让不可信的代码运行在一定的环境中,从而限制这些代码访问隔离区之外的资源。

先了解一下eval()和new Function()

  1. var a = 'global scope'
  2. function b(){
  3. var a = 'local scope'
  4. eval('console.log(a)') //local scope
  5. ;(new Function('','console.log(a)'))() //global scope
  6. }
  7. b()

从上面代码中可以看到,eval中的代码执行时的作用域为当前作用域。它可以访问到函数中的局部变量。
new Function中的代码执行时的作用域为全局作用域,不论它的在哪个地方调用的。所以它访问的是全局变量a。它根本无法访问b函数内的局部变量。

  1. var a = 'global scope'
  2. function b(){
  3. //var a = 'local scope'
  4. eval('console.log(a)') //global scope
  5. ;(new Function('','console.log(a)'))() //global scope
  6. }
  7. b();

当我们在b函数中不定义变量a时,两种方法的输出相同。这与上述结论并不冲突。因为代码执行时,对变量的查找是从内到外的(作用域链)。当eval中的代码执行时,它依然是优先从b函数内部查找a变量,当查找不到时,再到全局中查找a,这时找到的a当然是’global scope’

尽管可以使用 Function 构造函数创建函数,但最好不要使用它,因为用它定义函数比用传统方式要慢得多。不过,所有函数都应看作 Function 类的实例。

Function
每个 JavaScript 函数实际上都是一个 Function 对象。运行 (function(){}).constructor === Function // true 便可以得到这个结论。
构造函数
Function 构造函数创建一个新的 Function 对象。直接调用此构造函数可用动态创建函数,但会遇到和 eval 类似的的安全问题和(相对较小的)性能问题。然而,与 eval 不同的是,Function 创建的函数只能在全局作用域中运行。

image.png
JS沙箱 proxy
两种机制:

快照沙箱

一年前拍照,一年后再拍照,前后对比,把区别保存起来,然后就可以进行回到一年前,以及还原操作。
具体实现

  1. /**
  2. * sandbox.js文件
  3. * JS沙箱
  4. * 快照沙箱
  5. * */
  6. export default class SnapshotSandbox {
  7. constructor() {
  8. this.proxy = window;
  9. this.modifyPropsMap = {} //记录window上的修改
  10. this.active() // 激活沙箱
  11. }
  12. active() {
  13. this.windowSnapshot = {} // 拍照
  14. for (const key in window) {
  15. if (Object.hasOwnProperty.call(window, key)) {
  16. this.windowSnapshot[key] = window[key]
  17. }
  18. }
  19. Object.keys(this.modifyPropsMap).forEach(key => {
  20. window[key] = this.modifyPropsMap[key]
  21. })
  22. }
  23. inactive() {
  24. for (const key in window) {
  25. if (Object.hasOwnProperty.call(window, key)) {
  26. if (window[key] !== this.windowSnapshot[key]) {
  27. this.modifyPropsMap[key] = window[key] // 保存变化
  28. window[key] = this.windowSnapshot[key] // 还原变化
  29. }
  30. }
  31. }
  32. }
  33. }
  34. import SnapshotSandbox form 'sandbox.js'
  35. // 一个应用的运行,从开始到结束,切换后不会影响全局
  36. // let sandbox = new SnapshotSandbox();
  37. // ((window) => {
  38. // window.a = 1
  39. // window.b = 2
  40. // console.log(window.a, window.b);
  41. // sandbox.inactive()
  42. // console.log(window.a, window.b);
  43. // sandbox.active()
  44. // console.log(window.a, window.b);
  45. // })(sandbox.proxy) // sandbox.proxy 就是window

image.png
一个应用的运行,从开始到结束,切换后不会影响全局

微前端实现里,如果是多个子应用就不能使用这种方式了,存在有一个问题,比如当A应用切换到B应用的时候,不能确定对谁拍照,应为是两个不同的应用,显然是不合理的
使用es6的proxy

代理沙箱

代理沙箱可以实现多应用沙箱,把不同的应用用不同的代理来处理

  1. /**
  2. * Proxy代理沙箱
  3. */
  4. class ProxySandBox {
  5. constructor() {
  6. const rawWindow = window
  7. const fakeWindow = {}
  8. const proxy = new Proxy(fakeWindow, {
  9. set(target, p, value) {
  10. target[p] = value
  11. return true
  12. },
  13. get(target, p) {
  14. return target[p] || rawWindow[p] // 先在代理window上找, 没有,则取全局window
  15. }
  16. })
  17. this.proxy = proxy
  18. }
  19. }
  20. let sandbox1 = new ProxySandBox()
  21. let sandbox2 = new ProxySandBox()
  22. window.a = 1;
  23. ((window) => {
  24. window.a = 'hello';
  25. console.log(window.a);
  26. })(sandbox1.proxy);
  27. ((window) => {
  28. window.a = 'world';
  29. console.log(window.a);
  30. })(sandbox2.proxy);

image.png