js字符串转函数
对于函数型的字符串,我们想要将其转换为一个函数然后执行,有2种常用的方法。
1.eval()
eval()
具有可以解析表达式的特性,所以可以利用这一特性将字符串解析为一个函数。
let funcStr = "function test(value){alert(value)}";
let test = eval("(false || "+funcStr+")");
test("函数能够执行");
这里
eval
实际是解析了表达式(false || function test(value))
但是需要注意的事
eval
可以解析任何字符串,这是不安全的,请尽量不要使用。
2.new Function()
new Function ([arg1[, arg2[, ...argN]],] functionBody)
new Function()
只接受字符串参数,其可选参数为方法的入参,必填参数为方法体内容。function add(a, b) { retrun a + b; } //等价于 var add = new Function ('a', 'b', 'return a + b');
我们可以利用它最后的方法体参数,直接返回一个函数。那么执行这个新的创建函数后得到的就是我们需要的函数。
let funcStr = "function test(value){alert(value)}";
let funcTest = new Function('return '+funcStr);
funcTest()("函数也能够执行")
3.深入理解
如果是一个完整的函数体,且其中函数体内未包含其他函数,那么可以使用new Function
或eval
来实现。
如果函数体内包含函数,那么包含的函数必须和被调用的函数位于同一作用域下。
如何保证调用函数和调用参数在统一作用域?有2种方案可以采取:
- 将变量和方法都声明到window对象上,那么任何地方皆可以调用,但是这样存在风险,如果存在和window对象内置属性同名的情况下,将覆盖内置属性的值。
- 将变量和方法作用域一个实例对象中。
下面讲解如何用实例限制作用域。
- 首先创建一个构造方法,构造方法中声明变量和方法,如果存在动态变量时,可如下操作,将变量放在this对象上,这样在实例中即可通过
this.属性名
获取。 - 定义一个方法,调用字符串函数。
- 在方法内绑定this指向,并使用with省略表达式的前缀,例如
this._f
即可省略为_f
;
以let exp = _f("defaultStr")(_f("DFormat")(reportDate,'YYYY-MM-DD'))
字符串函数为例。
其中_f为一个函数,reportDate为一个变量
如果直接使用eval(exp)
,会报错_f is not defined
,因为默认情况下在严格模式下_f的作用域为undefined。
此时你可以设置window为作用域,例如:
//如果只是单纯的一个函数_f("DFormat")(reportDate,'YYYY-MM-DD')
//那么可与如下设置:
window._f = resolveFilter
expStr = `_f("DFormat")(reportDate,'YYYY-MM-DD')`
window.reportDate = "20191212";
let a = eval(expStr)
console.log(a)
将变量和方法绑定到window上,然后进行全局调用
但是上例是2个方法_f("defaultStr")(_f("DFormat")(reportDate,'YYYY-MM-DD'))
,函数调用时,立即执行
_f("DFormat")(reportDate,'YYYY-MM-DD')
,返回的结果不是一个函数,导致调用_f("defaultStr")
时,_f
方法被第二个函数的执行结果覆盖了,不再是一个方法了,所以不能继续执行。
要解决方法覆盖问题,那么方法就不能被定义为属性,而应该是原型,利用继承的特性解决该问题。
实例解决方案
//注册filter
export const resolveFilter = (id) => {
if (typeof id !== 'string') {
return id;
} else {
return filters[id]//返回一个函数
}
}
//创建实例对象
export function createExpInstance(paramsEntity) {
for (let key in paramsEntity) {
this[key] = paramsEntity[key]
}
}
createExpInstance.prototype._f = resolveFilter;
createExpInstance.prototype.getValue = function (exp) {
let code = `return (()=>{with (this) {return ${ exp}}})()`;
try {
let func = new Function(code);
let currentFunc = func.bind(this);//绑定函数this指向,严格模式下this为undefined,非严格模式为window
return currentFunc();
} catch (err) {
// console.log(err)
}
}
let expInstance = new createExpInstance(paramsEntity);
let expValue = expInstance.getValue(expStr);
with(this)
虽然是个不推荐属性,但是用在这里恰到好处,让我们省略了this的书写,从而直接利用实例属性和方法。
如果没有with(this)
,code
应为:
return (()=>{ return this._f("defaultStr")(this._f("DFormat")(this.reportDate,'YYYY-MM-DD'))})()