通过with和Proxy构建沙箱环境解决js脚本执行变量作用域问题

蛰伏已久 2021-11-03

前端执行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的对象中访问,如果访问到了,则使用

看起来好像解决了作用域问题,但是当访问一个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

在生成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)


-END-

点赞(0)