轉(zhuǎn):村長(zhǎng)筆記
JavaScript中function的動(dòng)態(tài)執(zhí)行
由于最近來(lái)自重構(gòu)中的需要,所以深入的研究了JavaScript中function(函數(shù)/方法)的動(dòng)態(tài)執(zhí)行。搜索了一下,發(fā)現(xiàn)在網(wǎng)上詢問(wèn)相關(guān)問(wèn)題的人非常多,相應(yīng)給出的解決方法也是很多的,但卻沒(méi)有深入研究的說(shuō)明。本人覺(jué)得深入的研究并解決function的動(dòng)態(tài)執(zhí)行問(wèn)題還是非常有價(jià)值的。
本文將從不同的應(yīng)用情況入手,并由淺入深的給出解決方案與分析。動(dòng)態(tài)執(zhí)行從服務(wù)端返回的JavaScript代碼不在本文的討論范圍內(nèi)。
場(chǎng)景1:動(dòng)態(tài)執(zhí)行無(wú)參數(shù)、無(wú)返回值function
這是最簡(jiǎn)單,也是最常見(jiàn)的case。這種場(chǎng)景下,使用eval或者setTimeout都是可以的。如下示例代碼:- function test() {
- alert('test');
- }
-
- eval("test()");
- setTimeout("test()", 0);
-
復(fù)制代碼 由于這種case是最簡(jiǎn)單的,如何去執(zhí)行參考示例即可。想多做一點(diǎn)說(shuō)明的是setTimeout這個(gè)方法。
對(duì)于目前的JavaScript引擎來(lái)說(shuō),都是單線程處理任務(wù)的(JavaScript engines only have a single thread)。John Resig在他的How JavaScript Timer Work中有詳細(xì)的說(shuō)明,這里不做累述。對(duì)于JavaScript的Timer來(lái)說(shuō),setTimeout與setInterval都是可以異步的執(zhí)行代碼的,但是它們卻在執(zhí)行時(shí)有非常本質(zhì)的區(qū)別(區(qū)別仍可參考How JavaScript Timer Work),被推薦使用的是setTimeout方法。既然setTimeout可以用來(lái)異步執(zhí)行JavaScript的代碼(function),那么我們一旦將它的第二個(gè)參數(shù)設(shè)置為0,即0毫秒后執(zhí)行第一個(gè)參數(shù)內(nèi)容的話,就相當(dāng)于立即異步執(zhí)行代碼。這樣,通過(guò)使用多個(gè)setTimeout方法,便以另一種形式在JavaScript中實(shí)現(xiàn)多線程。
場(chǎng)景2:動(dòng)態(tài)執(zhí)行簡(jiǎn)單類型參數(shù)、無(wú)返回值的function
這個(gè)case雖然要比場(chǎng)景1復(fù)雜一些,但是仍然非常簡(jiǎn)單即可完成。- function test(input) {
- alert(input);
- }
-
- var t1 = 1;
- eval("test(" + t1 + ")");
- var t2 = true;
- setTimeout("test(" + t2 + ")", 0);
復(fù)制代碼 場(chǎng)景3:動(dòng)態(tài)執(zhí)行復(fù)雜類型參數(shù)、無(wú)返回值的function
這個(gè)也不復(fù)雜,就怕想復(fù)雜了。如果按照?qǐng)鼍?中的方式來(lái)動(dòng)態(tài)執(zhí)行,那么多半你會(huì)比較郁悶,因?yàn)橄雮鬟f復(fù)雜類型的參數(shù)比較困難。簡(jiǎn)單的方式如下:- function test(input) {
- alert(input[0].name);
- }
-
- var t = [{'name' : 'db'}];
- eval("test(t)");
- setTimeout("test(t)", 0);
復(fù)制代碼 可以看出,只需要將變量直接寫(xiě)到function字符串的參數(shù)部分即可。這種書(shū)寫(xiě)方法比較依賴于既定義的變量,我們改變一下代碼再做一個(gè)測(cè)試,代碼如下:- function test(input) {
- alert(input[0].name);
- }
-
- function print() {
- var t = [{'name' : 'db'}];
- eval("test(t)");
- setTimeout("test(t)", 0);
- }
-
- print();
復(fù)制代碼 在不同的瀏覽器上,可以看到在eval輸出了一次之后,setTimeout要么是沒(méi)有輸出,要么就是提示說(shuō)“t未定義”。這是因?yàn)閟etTimeout第一個(gè)參數(shù),是存在作用域問(wèn)題。只有在全局作用域定義的函數(shù)和變量,才能被setTimeout使用。這一點(diǎn)一定要注意,如果非要使用非全局的函數(shù)與變量,只有考慮閉包來(lái)實(shí)現(xiàn)。
場(chǎng)景4:動(dòng)態(tài)執(zhí)行有返回值的function
在前邊一些case中,已經(jīng)解決了動(dòng)態(tài)執(zhí)行的function的傳參問(wèn)題,對(duì)于大多數(shù)的開(kāi)發(fā)者來(lái)說(shuō)就已經(jīng)足夠了。但是,在使用JavaScript進(jìn)行基于配置的功能開(kāi)發(fā)時(shí),可能還會(huì)遇到需要?jiǎng)討B(tài)執(zhí)行有返回值function的case。
之前幾個(gè)case中,我們實(shí)現(xiàn)動(dòng)態(tài)執(zhí)行時(shí),使用的是eval與setTimeout?梢蕴崆胺穸ǖ氖莝etTimeout,因?yàn)檫@個(gè)方法已經(jīng)固定了返回值,即當(dāng)前計(jì)時(shí)器的引用,用來(lái)清理計(jì)時(shí)器時(shí)使用。當(dāng)然,提前否定setTimeout讓很多人不甘心,并給出了如下方法得到返回值:
function test(input) {
return "result:" + input;
}
var t = 'test';
setTimeout("alert(test(t))", 0);
執(zhí)行后發(fā)現(xiàn),返回值的確被打印了出來(lái)。但是,如果我想使用這個(gè)返回值呢?我們將代碼變化一下:- function test(input) {
- return "result:" + input;
- }
- var t = 'test';
- var result = '';
- setTimeout("result = test(t)", 0);
- alert(result);
復(fù)制代碼 如果得到最后一行的輸出結(jié)果,所有人都會(huì)失望。前文已經(jīng)提過(guò),setTimeout是異步執(zhí)行的,所以在第一個(gè)參數(shù)中的JavaScript代碼執(zhí)行時(shí),result就已經(jīng)輸出了。顯然,setTimeout要處理這個(gè)問(wèn)題比較苦難。
eval在這個(gè)case中比較給力,能夠完成我們交給的任務(wù):- 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);
復(fù)制代碼 兩種寫(xiě)法均可以得到我們希望的結(jié)果。而且復(fù)雜一些的需要也能勝任:- 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);
復(fù)制代碼 還有一種方式,也可以滿足這個(gè)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);
復(fù)制代碼 但是,new Function的方式卻存在作用域的問(wèn)題,而且也需要借助閉包才能解決。問(wèn)題如下:- 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();
復(fù)制代碼 而eval就不存在作用域的問(wèn)題:- 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();
復(fù)制代碼 總結(jié):
在處理動(dòng)態(tài)執(zhí)行的問(wèn)題上,推薦使用eval。它是同步處理的,而且無(wú)作用域問(wèn)題,復(fù)雜參數(shù)、返回值均可滿足。 |