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

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

JS中沙箱的使用场景

前端JS中也会有应用到沙箱的时候,毕竟有时候你要获取到的是第三方的JS文件或数据?而这数据又是不一定可信的时候,创建沙箱,做好保险工作尤为重要。

1、jsonp:解析服务器所返回的jsonp请求时,如果不信任jsonp中的数据,可以通过创建沙箱的方式来解析获取数据;(TSW中处理jsonp请求时,创建沙箱来处理和解析数据);
2、执行第三方js:当你又必要执行第三方js的时候,而这份js文件又不一定可信的时候;
3、在线代码编辑器:相信大家都有使用过一些在线代码编辑器,而这些代码的执行,基本都会放置在沙箱中,放置对页面本身造成影响;(例如:https://codesandbox.io/s/new
4、vue的服务端渲染:vue的服务端渲染实现中,通过创建沙箱执行前端的bundle文件;在调用createBundleRenderer方法时候,允许配置runInNewContext为true或false的形式,判断是否传入一个新创建的sandbox对象以供vm使用;
5、vue模板中表达式计算:vue模板中表达式的计算被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不能够在模板表达式中试图访问用户定义的全局变量。
总而言之:当你要解析或执行不可信的JS的时候,当你要隔离被执行代码的执行环境的时候,当你要对执行代码中可访问对象进行限制的时候,沙箱就派上用场了。

沙箱应用,和防止XSS脚本攻击,CSRF跨站请求伪造

  • new Function + with
  • 借助iframe实现沙箱
  • nodejs中的沙箱

先了解一下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函数内的局部变量。

var a = 'global scope'
function b(){
  //var a = 'local scope' 
  eval('console.log(a)') //global scope
  ;(new Function('','console.log(a)'))() //global scope
}
b();

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

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

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

JS沙箱 ,两种机制,以及在微前端中的应用

image.png

快照沙箱

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

/**
 * sandbox.js文件
 * JS沙箱
 * 快照沙箱
 * */

export default class SnapshotSandbox {
  constructor() {
    this.proxy = window;
    this.modifyPropsMap = {} //记录window上的修改
    this.active() // 激活沙箱
  }
  active() {
    this.windowSnapshot = {} // 拍照
    for (const key in window) {
      if (Object.hasOwnProperty.call(window, key)) {
        this.windowSnapshot[key] = window[key]
      }
    }
    Object.keys(this.modifyPropsMap).forEach(key => {
      window[key] = this.modifyPropsMap[key]
    })
  }
  inactive() {
    for (const key in window) {
      if (Object.hasOwnProperty.call(window, key)) {
        if (window[key] !== this.windowSnapshot[key]) {
          this.modifyPropsMap[key] = window[key] // 保存变化
          window[key] = this.windowSnapshot[key] // 还原变化
        }
      }
    }
  }
}

import SnapshotSandbox form 'sandbox.js'

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

// let sandbox = new SnapshotSandbox();
// ((window) => {
//   window.a = 1
//   window.b = 2
//   console.log(window.a, window.b);
//   sandbox.inactive()
//   console.log(window.a, window.b);
//   sandbox.active()
//   console.log(window.a, window.b);
// })(sandbox.proxy) // sandbox.proxy 就是window

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

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

代理沙箱

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

/**
 * Proxy代理沙箱
 */
class ProxySandBox {
  constructor() {
    const rawWindow = window
    const fakeWindow = {}
    const proxy = new Proxy(fakeWindow, {
      set(target, p, value) {
        target[p] = value
        return true
      },
      get(target, p) {
        return target[p] || rawWindow[p] // 先在代理window上找, 没有,则取全局window
      }
    })
    this.proxy = proxy
  }
}

let sandbox1 = new ProxySandBox()
let sandbox2 = new ProxySandBox()

window.a = 1;

((window) => {
  window.a = 'hello';
  console.log(window.a);
})(sandbox1.proxy);

((window) => {
  window.a = 'world';
  console.log(window.a);
})(sandbox2.proxy);

image.png

相关链接:https://segmentfault.com/a/1190000020463234