8
回答
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。它是同步处理的,而且无作用域问题,复杂参数、返回值均可满足。

举报
鉴客
发帖于7年前 8回/3K+阅
顶部