with 全代理沙箱
1// 监控执行代码2function compileCode(src) {3 src = `with (exposeObj){${src}}`;4 return new Function("exposeObj", src);5}6// 代理对象7function proxyObj(originObj) {8 let exposeObj = new Proxy(originObj, {9 has: (target, key) => {10 if (["console", "Math", "Date"].indexOf(key) >= 0) {11 return target[key];12 }13 if (!target.hasOwnProperty(key)) {14 throw new Error(`${target}上不存在${key}`);15 }16 return target[key];17 },18 });19 return exposeObj;20}21// 创建沙盒22function createSandbox(src, obj) {23 let proxy = proxyObj(obj);24 compileCode(src).call(proxy, proxy);25}2627const testObj = {28 value: 1,29 a: {30 b: 2,31 },32 c: "3",33};34const c = "c";35createSandbox(`value='32323';console.log(c);`, testObj);
快照 Snapshot 沙箱
核心原理是激活当前沙箱时把前宿主环境的全局变量备份一下,并把上一次对该沙箱做的更改恢复一下(若存在)
失活时找出当次沙箱和备份的全局变量不同的属性,存储一下,并把存储的宿主环境恢复一下
哈,双缓存策略
1class SnapshotSandBox {2 constructor(name) {3 this.modifyMap = {}; // 存放修改的属性4 this.windowSnapshot = {};5 }6 active() {7 // 缓存active状态的沙箱8 this.windowSnapshot = {};9 for (const item in window) {10 this.windowSnapshot[item] = window[item];11 }1213 Object.keys(this.modifyMap).forEach((p) => {14 window[p] = this.modifyMap[p];15 });16 }17 inactive() {18 for (const item in window) {19 if (this.windowSnapshot[item] !== window[item]) {20 // 记录变更21 this.modifyMap[item] = window[item];22 // 还原window23 window[item] = this.windowSnapshot[item];24 }25 }26 }27}28window.a = "1";29const diffSandbox = new SnapshotSandBox();30diffSandbox.active(); // 激活沙箱31debugger;32window.a = "0";33console.log("开启沙箱:", window.a);34diffSandbox.inactive(); //失活沙箱35debugger;36console.log("失活沙箱:", window.a);37diffSandbox.active(); // 重新激活38debugger;39console.log("再次激活", window.a);
这种方式也无法支持多实例,因为运行期间所有的属性都是保存在 window 上的。
代理 Proxy 沙箱
单实例
1class 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) =>15 this.setWindowProp(prop, value)16 );17 }18 inactive() {19 this.modifiedPropsOriginalValueMapInSandbox.forEach((value, prop) =>20 this.setWindowProp(prop, value)21 );22 this.addedPropsMapInSandbox.forEach((_, prop) =>23 this.setWindowProp(prop, undefined, true)24 );25 }26 constructor() {27 const fakeWindow = Object.create(null);28 this.proxyWindow = new Proxy(fakeWindow, {29 set: (target, prop, value, receiver) => {30 const originalVal = window[prop];31 if (!window.hasOwnProperty(prop)) {32 this.addedPropsMapInSandbox.set(prop, value);33 } else if (!this.modifiedPropsOriginalValueMapInSandbox.has(prop)) {34 this.modifiedPropsOriginalValueMapInSandbox.set(prop, originalVal);35 }36 this.currentUpdatedPropsValueMap.set(prop, value);37 window[prop] = value;38 },39 get: (target, prop, receiver) => {40 return target[prop];41 },42 });43 }44}45// 验证:46let legacySandBox = new LegacySandBox();47legacySandBox.active();48legacySandBox.proxyWindow.city = "Beijing";49console.log("window.city-01:", window.city);50legacySandBox.inactive();51console.log("window.city-02:", window.city);52legacySandBox.active();53console.log("window.city-03:", window.city);54legacySandBox.inactive();55// 输出:56// window.city-01: Beijing57// window.city-02: undefined58// window.city-03: Beijing
多实例
1class MultipleProxySandbox {2 active() {3 this.sandboxRunning = true;4 }5 inactive() {6 this.sandboxRunning = false;7 }8 constructor() {9 const rawWindow = window;10 const fakeWindow = {};11 const proxy = new Proxy(fakeWindow, {12 set: (target, prop, value) => {13 if (this.sandboxRunning) {14 target[prop] = value;15 return true;16 }17 },18 get: (target, prop) => {19 // 如果fakeWindow里面有,就从fakeWindow里面取,否则,就从外部的window里面取20 let value = prop in target ? target[prop] : rawWindow[prop];21 return value;22 },23 });24 this.proxy = proxy;25 }26}2728const context = { document: window.document };2930const newSandBox1 = new MultipleProxySandbox("代理沙箱1", context);31newSandBox1.active();32const proxyWindow1 = newSandBox1.proxy;3334const newSandBox2 = new MultipleProxySandbox("代理沙箱2", context);35newSandBox2.active();36const proxyWindow2 = newSandBox2.proxy;37console.log(38 "共享对象是否相等",39 window.document === proxyWindow1.document,40 window.document === proxyWindow2.document41);4243proxyWindow1.a = "1"; // 设置代理1的值44proxyWindow2.a = "2"; // 设置代理2的值45window.a = "3"; // 设置window的值46console.log("打印输出的值", proxyWindow1.a, proxyWindow2.a, window.a);4748newSandBox1.inactive();49newSandBox2.inactive(); // 两个沙箱都失活5051proxyWindow1.a = "4"; // 设置代理1的值52proxyWindow2.a = "4"; // 设置代理2的值53window.a = "4"; // 设置window的值54console.log("失活后打印输出的值", proxyWindow1.a, proxyWindow2.a, window.a);5556newSandBox1.active();57newSandBox2.active(); // 再次激活5859proxyWindow1.a = "4"; // 设置代理1的值60proxyWindow2.a = "4"; // 设置代理2的值61window.a = "4"; // 设置window的值62console.log("失活后打印输出的值", proxyWindow1.a, proxyWindow2.a, window.a);
可以看出最后一种实现是最优实现,既没有操作 window,又能实现多实例,代码又精简,通俗易懂 👍🏻