likes
comments
collection
share

用"Function"替代"eval"进行动态代码生成

作者站长头像
站长
· 阅读数 21

一、背景

 在JavaScript中我们有时会遇到这么一个场景:"函数可能要根据不可预知的条件执行代码"。比如说计算器中的加减乘除,用户无法直接将操作符"+、-、*、/"作为参数传入函数中,因此我们一般会采取字符串形式的操作符,但如此操作同样会存在一个问题,我们也无法使用策略模式(Map集合匹配操作符)。

 当然在这种情况下,我们可以使用switch语句来实现字符串的一一匹配。不过这种方式必然存在的问题便是,当换一个场景,用户可能输入的"条件"数量大起来之后,代码量便会不停的增加并不易于维护。

 那么我们会想,能不能用"通用性"代码来处理这个问题呢?首先我们会想到的便是处理"动态代码生成"的方法"eval"。

二、Eval 动态生成代码

 使用Eval,我们便可以使用字符串拼接的方式,使得这个不可预知条件函数的正常执行,举个栗子:

function calculate(firstOperand, operation, secondOperand) {
  return eval('firstOperand' + operation + 'secondOperand');
}

calculate(1, '+', 2); // -> result: 3
calculate(1, '>', 2); // -> result: false

 上述例子中,我们通过eval实现了一个简易计算器的功能,使之用户可以传入字符串形式的比较符,同样的,当用户传入的符号为比较运算符时,也能得到对应的结果。

 虽然说Eval能够完成我们的目标,但并不是推荐使用Eval来进行动态代码生成,因为Eval在非严格模式下存在较为严重的"副作用",副作用简单来说就是当前函数的执行会导致外界作用域的改变。副作用的问题可以通过开启严格模式来得到一定的缓解。同时Eval也存在一定的性能问题。

三、Function 构造函数

 相较于Eval,我们更推荐使用Function构造函数来创建一个函数,那它相比于Eval有哪些优势呢?首先new Function创建的函数是无法访问到当前闭包的局部变量,只能访问全局作用域,减少了副作用的影响。同时使用new Function具备更好的维护性,在使用eval中使用到外部函数的相关变量时在编辑器中我们是无法看见形参的高亮,也就无法准确判断该参数是否被使用,如下:

用"Function"替代"eval"进行动态代码生成

 在使用Function构造函数来创建则可以避免这个问题,使整体代码更易于维护,将上述的eval代码修改为Function构造函数创建的动态代码如下:

function calculate(firstOperand, operation, secondOperand) {
  const calculateFn = new Function('firstOperand', 'secondOperand', 
  	'return firstOperand' + operation + 'secondOperand;');
  return calculateFn(firstOperand, secondOperand);
}

console.log(calculate(1, '+', 3));

 当然了,你也可以将operation作为参数传入,同样的在函数体的部分你也可以通过模板字符串来完成,这可以使得代码更具可读性。

 另外提一句,使用Function构造函数的情况下,我们能够在严格模式下使用with关键字,代码示例如下:

用"Function"替代"eval"进行动态代码生成

 在eval中是无法使用的,这点大家可以自行测试。

四、总结

 总之,在需要使用到动态代码生成的时候,我们有两个方法:"eval"与"Function",相较而言更为推荐使用Function构造函数,不止因为他在安全、可维护性上优秀于Eval,同时也是因为在有需求的情况下,在严格模式下能够使用with语法。