【转】JavaScript 中 function 的动态执行

由于最近来自重构中的需要,所以深入的研究了JavaScript中function(函数/方法)的动态执行。搜索了一下,发现在网上询问相关问题的人非常多,相应给出的解决方法也是很多的,但却没有深入研究的说明。本人觉得深入的研究并解决function的动态执行问题还是非常有价值的。

本文将从不同的应用情况入手,并由浅入深的给出解决方案与分析。动态执行从服务端返回的JavaScript代码不在本文的讨论范围内。

场景1:动态执行无参数、无返回值function

这是最简单,也是最常见的case。这种场景下,使用eval或者setTimeout都是可以的。如下示例代码:

1 function test() {
2     alert('test');
3 }
4  
5 eval("test()");
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复杂一些,但是仍然非常简单即可完成。

1 function test(input) {
2     alert(input);
3 }
4  
5 var t1 = 1;
6 eval("test(" + t1 + ")");
7 var t2 = true;
8 setTimeout("test(" + t2 + ")", 0);

场景3:动态执行复杂类型参数、无返回值的function 

这个也不复杂,就怕想复杂了。如果按照场景2中的方式来动态执行,那么多半你会比较郁闷,因为想传递复杂类型的参数比较困难。简单的方式如下:

1 function test(input) {
2     alert(input[0].name);
3 }
4  
5 var t = [{'name' : 'db'}];
6 eval("test(t)");
7 setTimeout("test(t)", 0);

可以看出,只需要将变量直接写到function字符串的参数部分即可。这种书写方法比较依赖于既定义的变量,我们改变一下代码再做一个测试,代码如下:

01 function test(input) {
02     alert(input[0].name);
03 }
04  
05 function print() {
06     var t = [{'name' : 'db'}];
07     eval("test(t)");
08     setTimeout("test(t)", 0);
09 }
10  
11 print();

在不同的浏览器上,可以看到在eval输出了一次之后,setTimeout要么是没有输出,要么就是提示说“t未定义”。这是因为setTimeout第一个参数,是存在作用域问题。只有在全局作用域定义的函数和变量,才能被setTimeout使用。这一点一定要注意,如果非要使用非全局的函数与变量,只有考虑闭包来实现。

场景4:动态执行有返回值的function
在前边一些case中,已经解决了动态执行的function的传参问题,对于大多数的开发者来说就已经足够了。但是,在使用JavaScript进行基于配置的功能开发时,可能还会遇到需要动态执行有返回值function的case。

之前几个case中,我们实现动态执行时,使用的是eval与setTimeout。可以提前否定的是setTimeout,因为这个方法已经固定了返回值,即当前计时器的引用,用来清理计时器时使用。当然,提前否定setTimeout让很多人不甘心,并给出了如下方法得到返回值:

1 function test(input) {
2     return "result:" + input;
3 }
4 var t = 'test';
5 setTimeout("alert(test(t))", 0);

执行后发现,返回值的确被打印了出来。但是,如果我想使用这个返回值呢?我们将代码变化一下:

1 function test(input) {
2     return "result:" + input;
3 }
4 var t = 'test';
5 var result = '';
6 setTimeout("result = test(t)", 0);
7 alert(result);

如果得到最后一行的输出结果,所有人都会失望。前文已经提过,setTimeout是异步执行的,所以在第一个参数中的JavaScript代码执行时,result就已经输出了。显然,setTimeout要处理这个问题比较苦难。

eval在这个case中比较給力,能够完成我们交给的任务:

01 function test(input) {
02     return "result:" + input;
03 }
04 var result;
05 var t = 'tttt';
06 result = eval("test(t)");
07 alert(result);
08  
09 t = '2222';
10 eval("result = test(t)");
11 alert(result);

两种写法均可以得到我们希望的结果。而且复杂一些的需要也能胜任:

01 Test = function() {
02 };
03 Test.prototype = {
04     test : function(input) {
05         return "result:" + input;
06     }
07 };
08  
09 var te = new Test();
10 var result;
11 var t = 'tttt';
12 result = eval("te.test(t)");
13 alert(result);

还有一种方式,也可以满足这个case:

01 Test = function() {
02 };
03 Test.prototype = {
04     test : function(input) {
05         return "result:" + input;
06     }
07 };
08  
09 var t = 'tttt';
10 var te = new Test();
11 var value = new Function("return te.test(t)")();
12  
13 alert(value);

但是,new Function的方式却存在作用域的问题,而且也需要借助闭包才能解决。问题如下:

01 Test = function() {
02 };
03 Test.prototype = {
04     test : function(input) {
05         return "result:" + input;
06     }
07 };
08  
09 function run() {
10     var t = 'tttt';
11     var te = new Test();
12     var value = new Function("return te.test(t)")();
13  
14     alert(value);
15 }
16  
17 run();

而eval就不存在作用域的问题:

01 Test = function() {
02 };
03 Test.prototype = {
04  test : function(input) {
05   return "result:" + input;
06  }
07 };
08  
09 function run() {
10  var te = new Test();
11  var result;
12  var t = 'tttt';
13  result = eval("te.test(t)");
14  alert(result);
15 }
16  
17 run();

总结:
在处理动态执行的问题上,推荐使用eval。它是同步处理的,而且无作用域问题,复杂参数、返回值均可满足。

原文地址:http://www.oschina.net/question/54100_22666

原文地址:https://www.cnblogs.com/xlhblogs/p/2566096.html