js将字符串函数转可执行的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'))})()