通过with和Proxy构建沙箱环境解决js脚本执行变量作用域问题
在开发前端时,有时会遇到执行字符串脚本函数的场景,如给一个字符串"function(a,b){return a+b}",
一般我们可以通过两种方式实现:eval 或者 new Function
eval方式并不推荐,首先在写法上就很奇怪,eval不会返回一个函数,如 let a = eval("function(a,b){return a+b}")
这样会报错,第一个就是必须给函数声明一个name,第二个就是即使声明了一个name,也不会返回一个函数,而是直接通过函数name调用
let str = "function a(a,b){ return a+b }" console.log(eval(str)) console.log(a(1,2)) // 上下文中并没有变量a,而是eval时产生的,很奇怪的写法
通过new Function 可以根据参数和函数体创建一个函数
let a = new Function('a,b','return a + b') a(1,2)
形式很优美,问题就转换为了从字符串函数中,提取参数和函数体,然后构造一个函数
可以通过正则匹配参数和函数体
let stringFunToJsFun = function (stringFun='') { const reg = /function.*?\((.*?)\).*?\{(.*)\}/s; const match = stringFun.trim().match(reg); if (match) { const arg = match[1]; const fun = match[2]; const jsFun = new Function(arg, fun); return jsFun; } return stringFun; }; let a = stringFunToJsFun("function a(a,b){ return a+b }") a(1,2)
如果脚本是用户写的,而且是在服务器运行,那么这么做会产生很大的安全隐患,如用户在脚本中操纵环境中的全局变量或方法,比如process这个变量,就可以在脚本中进行读写,有很大的安全隐患,我们希望在脚本中只能访问特定的变量,不能随意访问作用域外的变量。可通过with和proxy来实现
使用with后,当调用某个变量时,会先到with的对象中访问,如果访问到了,则使用
看起来好像解决了作用域问题,但是当访问一个with对象不存在的变量时,还是会向外层作用域查找,还是解决不了全局变量被意外访问和修改的问题。
let globalVal = 1 let sandbox = { a:1, } function test() { with(sandbox){ console.log(a) globalVal = 0 //sandbox中没有,会在外层作用域查找 } } test() console.log(globalVal) //0,被修改了
我们希望在查找globalVal的时候,只在sanbox中查找,不要向上查找,但是由于sanbox是个普通对象,我们也无法控制,因此考虑使用Proxy
在生成Proxy实例对象时,我们设置两个handle,一个get,一个has,当with中访问某个变量时,会首先触发has方法,我们都返回true,告诉系统这个属性我有,不要再去外层作用域查找了;然后进入get方法,返回代理的对象中的这个属性,如果没有返回undefined,这样在with中,就无法操纵外层作用域中的变量了,只能操作代理的target中给定的有限的变量。
let globalVal = 1 let sandbox = new Proxy( { a:1, }, { get(target, p, receiver) { return target[p] }, has(target, p) { return true } } ) function test() { with(sandbox){ globalVal = 0 } } test() console.log(globalVal)
点赞(0)