JavaScript中function的动态执行
由于最近来自重构中的需要,所以深入的研究了JavaScript中function(函数/方法)的动态执行。搜索了一下,发现在网上询问相关问题的人非常多,相应给出的解决方法也是很多的,但却没有深入研究的说明。本人觉得深入的研究并解决function的动态执行问题还是非常有价值的。
本文将从不同的应用情况入手,并由浅入深的给出解决方案与分析。动态执行从服务端返回的JavaScript代码不在本文的讨论范围内。
场景1:动态执行无参数、无返回值function
这是最简单,也是最常见的case。这种场景下,使用eval或者setTimeout都是可以的。如下示例代码:
function test() { alert('test'); } eval("test()"); 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复杂一些,但是仍然非常简单即可完成。
function test(input) { alert(input); } var t1 = 1; eval("test(" + t1 + ")"); var t2 = true; setTimeout("test(" + t2 + ")", 0);
场景3:动态执行复杂类型参数、无返回值的function
这个也不复杂,就怕想复杂了。如果按照场景2中的方式来动态执行,那么多半你会比较郁闷,因为想传递复杂类型的参数比较困难。简单的方式如下:
function test(input) { alert(input[0].name); } var t = [{'name' : 'db'}]; eval("test(t)"); setTimeout("test(t)", 0);
可以看出,只需要将变量直接写到function字符串的参数部分即可。这种书写方法比较依赖于既定义的变量,我们改变一下代码再做一个测试,代码如下:
function test(input) { alert(input[0].name); } function print() { var t = [{'name' : 'db'}]; eval("test(t)"); setTimeout("test(t)", 0); } print();
在不同的浏览器上,可以看到在eval输出了一次之后,setTimeout要么是没有输出,要么就是提示说“t未定义”。这是因为setTimeout第一个参数,是存在作用域问题。只有在全局作用域定义的函数和变量,才能被setTimeout使用。这一点一定要注意,如果非要使用非全局的函数与变量,只有考虑闭包来实现。
场景4:动态执行有返回值的function
在前边一些case中,已经解决了动态执行的function的传参问题,对于大多数的开发者来说就已经足够了。但是,在使用JavaScript进行基于配置的功能开发时,可能还会遇到需要动态执行有返回值function的case。
之前几个case中,我们实现动态执行时,使用的是eval与setTimeout。可以提前否定的是setTimeout,因为这个方法已经固定了返回值,即当前计时器的引用,用来清理计时器时使用。当然,提前否定setTimeout让很多人不甘心,并给出了如下方法得到返回值:
function test(input) { return "result:" + input; } var t = 'test'; setTimeout("alert(test(t))", 0);
执行后发现,返回值的确被打印了出来。但是,如果我想使用这个返回值呢?我们将代码变化一下:
function test(input) { return "result:" + input; } var t = 'test'; var result = ''; setTimeout("result = test(t)", 0); alert(result);
如果得到最后一行的输出结果,所有人都会失望。前文已经提过,setTimeout是异步执行的,所以在第一个参数中的JavaScript代码执行时,result就已经输出了。显然,setTimeout要处理这个问题比较苦难。
eval在这个case中比较給力,能够完成我们交给的任务:
function test(input) { return "result:" + input; } var result; var t = 'tttt'; result = eval("test(t)"); alert(result); t = '2222'; eval("result = test(t)"); alert(result);
两种写法均可以得到我们希望的结果。而且复杂一些的需要也能胜任:
Test = function() { }; Test.prototype = { test : function(input) { return "result:" + input; } }; var te = new Test(); var result; var t = 'tttt'; result = eval("te.test(t)"); alert(result);
还有一种方式,也可以满足这个case:
Test = function() { }; Test.prototype = { test : function(input) { return "result:" + input; } }; var t = 'tttt'; var te = new Test(); var value = new Function("return te.test(t)")(); alert(value);
但是,new Function的方式却存在作用域的问题,而且也需要借助闭包才能解决。问题如下:
Test = function() { }; Test.prototype = { test : function(input) { return "result:" + input; } }; function run() { var t = 'tttt'; var te = new Test(); var value = new Function("return te.test(t)")(); alert(value); } run();
而eval就不存在作用域的问题:
Test = function() { }; Test.prototype = { test : function(input) { return "result:" + input; } }; function run() { var te = new Test(); var result; var t = 'tttt'; result = eval("te.test(t)"); alert(result); } run();
总结:
在处理动态执行的问题上,推荐使用eval。它是同步处理的,而且无作用域问题,复杂参数、返回值均可满足。