本文将从不同的应用情况入手,并由浅入深的给出解决方案与分析。动态执行从服务端返回的JavaScript代码不在本文的讨论范围内。
场景1:动态执行无参数、无返回值function
这是最简单,也是最常见的case。这种场景下,使用eval或者setTimeout都是可以的。如下示例代码:
6 | setTimeout( "test()" , 0); |
由于这种case是最简单的,如何去执行参考示例即可。想多做一点说明的是setTimeout这个方法。
对于目前的JavaScript引擎来说,都是单线程处理任务的(JavaScript engines only have a single thread)。John Resig在他的How JavaScript Timer Work中有详细的说明,这里不做累述。对于JavaScript的Timer来说,setTimeout与setInterval都是可以异步的执行代码的,但是它们却在执行时有非常本质的区别(区别仍可参考How JavaScript Timer Work),被推荐使用的是setTimeout方法。既然setTimeout可以用来异步执行JavaScript的代码(function),那么我们一旦将它的第二个参数设置为0,即0毫秒后执行第一个参数内容的话,就相当于立即异步执行代码。这样,通过使用多个setTimeout方法,便以另一种形式在JavaScript中实现多线程。
场景2:动态执行简单类型参数、无返回值的function
这个case虽然要比场景1复杂一些,但是仍然非常简单即可完成。
6 | eval( "test(" + t1 + ")" ); |
8 | setTimeout( "test(" + t2 + ")" , 0); |
场景3:动态执行复杂类型参数、无返回值的function
这个也不复杂,就怕想复杂了。如果按照场景2中的方式来动态执行,那么多半你会比较郁闷,因为想传递复杂类型的参数比较困难。简单的方式如下:
5 | var t = [{ 'name' : 'db' }]; |
7 | setTimeout( "test(t)" , 0); |
可以看出,只需要将变量直接写到function字符串的参数部分即可。这种书写方法比较依赖于既定义的变量,我们改变一下代码再做一个测试,代码如下:
06 | var t = [{ 'name' : 'db' }]; |
08 | setTimeout( "test(t)" , 0); |
在不同的浏览器上,可以看到在eval输出了一次之后,setTimeout要么是没有输出,要么就是提示说“t未定义”。这是因为setTimeout第一个参数,是存在作用域问题。只有在全局作用域定义的函数和变量,才能被setTimeout使用。这一点一定要注意,如果非要使用非全局的函数与变量,只有考虑闭包来实现。
场景4:动态执行有返回值的function
在前边一些case中,已经解决了动态执行的function的传参问题,对于大多数的开发者来说就已经足够了。但是,在使用JavaScript进行基于配置的功能开发时,可能还会遇到需要动态执行有返回值function的case。
之前几个case中,我们实现动态执行时,使用的是eval与setTimeout。可以提前否定的是setTimeout,因为这个方法已经固定了返回值,即当前计时器的引用,用来清理计时器时使用。当然,提前否定setTimeout让很多人不甘心,并给出了如下方法得到返回值:
2 | return "result:" + input; |
5 | setTimeout( "alert(test(t))" , 0); |
执行后发现,返回值的确被打印了出来。但是,如果我想使用这个返回值呢?我们将代码变化一下:
2 | return "result:" + input; |
6 | setTimeout( "result = test(t)" , 0); |
如果得到最后一行的输出结果,所有人都会失望。前文已经提过,setTimeout是异步执行的,所以在第一个参数中的JavaScript代码执行时,result就已经输出了。显然,setTimeout要处理这个问题比较苦难。
eval在这个case中比较給力,能够完成我们交给的任务:
02 | return "result:" + input; |
06 | result = eval( "test(t)" ); |
10 | eval( "result = test(t)" ); |
两种写法均可以得到我们希望的结果。而且复杂一些的需要也能胜任:
04 | test : function (input) { |
05 | return "result:" + input; |
12 | result = eval( "te.test(t)" ); |
还有一种方式,也可以满足这个case:
04 | test : function (input) { |
05 | return "result:" + input; |
11 | var value = new Function( "return te.test(t)" )(); |
但是,new Function的方式却存在作用域的问题,而且也需要借助闭包才能解决。问题如下:
04 | test : function (input) { |
05 | return "result:" + input; |
12 | var value = new Function( "return te.test(t)" )(); |
而eval就不存在作用域的问题:
04 | test : function (input) { |
05 | return "result:" + input; |
13 | result = eval( "te.test(t)" ); |
总结:
在处理动态执行的问题上,推荐使用eval。它是同步处理的,而且无作用域问题,复杂参数、返回值均可满足。