一、问题产生
本人在写一个用户可以自定义编写代码逻辑的软件。对于某个代码块,需要执行用户自己编写的javascript代码(字符串),自然而然的就用到了eval。对此我声明了一个处理类,主要逻辑函数如下。
export default class DBCCondition extends ConditionBase {
constructor(past_results, cdata) {
this.cdata = cdata;
}
//--过滤组合
filter(result) {
let isok =true;
eval(this.cdata.functions.filter);
return isok;
}
};
对于this.cdata.functions.filter
这个是用户自己编写的字符串代码,根据result的值,对isok
属性进行修改。
//--filter的一个值如下
filter:"isok = result.getBlueSum() > 10"
实际环境中,DBCCondition
类对象函数filter
会被调用数以百万次。对于eval,我发现每次调用浏览器都会去分配一段内存去执行内部代码。自然而然,数以百万次的eval调用,2个G的浏览器内存会被瞬间吃光。导致进程卡死,无法进行我接下来的处理逻辑。
二、探索
网上查阅了不少关于eval的资料,找了半天,却发现并没有关于解决大量调用eval产生的内存泄漏相关的文章。我突然想到,何不将用户自定义的部分定义成函数,这样在DBCCondition
类对象初始化的时候,就将函数解出为Function
?这样eval就只执行了一次。
尝试1
修改DBCCondition
类:
export default class DBCCondition extends ConditionBase {
constructor(past_results, cdata) {
this.cdata = cdata;
this._inner_filter = funcion(result){
let isok =true;
eval(this.cdata.functions.filter);
return isok;
}
}
//--过滤组合
filter(result) {
return this._inner_filter(result);
}
};
写完一看才发现不行。_inner_filter
内的eval还是会被执行百万次,浏览器卡死。崩溃。
尝试2
查阅eval语法,发现可以传递一个js函数,这样可以直接返回Function
:
var fn = eval(function(){
console.log("excuted");
})
fn();// 输出"excuted"
但是我不可能让用户改我的代码。用户传递过来的永远是字符串。兴冲冲的用字符串函数尝试:
var fn = eval("function(){console.log('excuted');}");
果然,语法都过不去。
尝试3
继续翻阅资料,发现eval函数可以在window
的全局域执行。本着试试的方式,我在控制台敲出了如下代码:
var fn = eval("window.__g__= function(){console.log('excuted');}");
居然没有报错!这确实是一个可执行的js语句,并且成功进行了运行。查看fn的值:
ok~我成功通过字符串转换为了一个Function
对象!
这下就好办了。修改DBCCondition
类:
export default class DBCCondition extends ConditionBase {
constructor(past_results, cdata) {
this.cdata = cdata;
this._inner_filter = eval(`window.__temp__var__=${this.cdata.functions.filter}`)
}
//--过滤组合
filter(result) {
return this._inner_filter(result);
}
};
当然 还得修改用户自定义的字符串函数,将原本直接操作isok
的逻辑改为返回,再传入result
参数
//--filter的一个值如下
filter:"function(result){return result.getBlueSum() > 10}"
编译、运行。一切正常。
三、结语
eval本是不建议使用的。网上都说用eval 99.99%的情况都有其他解决方案代替。但是我这个好像就是那0.01%。如果有博友觉得我这种情况也可以不采用eval解决,欢迎评论啊~