沙箱,即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()
var a = 'global scope'function b(){var a = 'local scope'eval('console.log(a)') //local scope;(new Function('','console.log(a)'))() //global scope}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沙箱 ,两种机制,以及在微前端中的应用
快照沙箱
一年前拍照,一年后再拍照,前后对比,把区别保存起来,然后就可以进行回到一年前,以及还原操作。
具体实现
/**
* 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

一个应用的运行,从开始到结束,切换后不会影响全局
微前端实现里,如果是多个子应用就不能使用这种方式了,存在有一个问题,比如当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);

