引言
沙箱,即sandbox。
就是让你的程序跑在一个隔离的环境下,不对外界的其他程序造成影响,通过创建类似沙盒的独立作业环境,在其内部运行的程序并不能对硬盘产生永久性的影响。
比如浏览器的每个tab页,就是一个沙箱。
渲染进程被沙箱(Sandbox)隔离,网页 web 代码内容必须通过 IPC 通道才能与浏览器内核进程通信,通信过程会进行安全的检查。沙箱设计的目的是为了让不可信的代码运行在一定的环境中,从而限制这些代码访问隔离区之外的资源。
先了解一下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 创建的函数只能在全局作用域中运行。
快照沙箱
一年前拍照,一年后再拍照,前后对比,把区别保存起来,然后就可以进行回到一年前,以及还原操作。
具体实现
/**
* 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);