iT邦幫忙

1

請問關於JavaScript呼叫Function時,有括弧與無括弧的差異

今天在試著做浮動的圖片時,
偶然發現了這個問題,正常的程式碼請看:

http://jsfiddle.net/wllai2001/6jAkt/

其中呼叫遞迴的:

setTimeout(loop, 4000);

若將呼叫的Function改成:

setTimeout(loop(), 4000);

即會發生以下的錯誤:
Uncaught RangeError: Maximum call stack size exceeded

但動作仍能正常執行,不知是否有大大能替在下解答這個疑問呢?
有呼叫Function時,有括弧與無括弧的差異,感謝各位大大。

嗯...附帶一提,我是用Chrome瀏覽器在看的。

wordsmith iT邦高手 1 級 ‧ 2013-06-10 15:17:54 檢舉
回答的大大都說的很好,我就觀念小小補充一下。

JavaScript的function,其實就和其他JS的物件一樣,可以當作參數傳給其他的function(如本例的setTimeout)。

而function加上()的意義,就是執行函式的意思,但是如果沒有加上(),就是把function傳來傳去而已。

2 個回答

11
fillano
iT邦超人 1 級 ‧ 2013-06-10 14:39:41
最佳解答

setTimeout(loop(), 4000)的意思是:
直接執行loop函數,然後把執行結果傳給setTimeout...但是因為loop執行會造成遞迴,所以來不及執行完,就因為執行太多遞迴函數而堆疊溢位了。

也就是說,原程式是把函數傳給setTimeout,叫他四秒後執行,但是你卻在傳給他執行執行了,所以出問題。

fillano iT邦超人 1 級 ‧ 2013-06-10 14:40:58 檢舉

少打個字:「傳給他執行執行了」->「傳給他執行前執行了」

14
bizpro
iT邦大師 1 級 ‧ 2013-06-10 15:02:42

setTimeout(loop, 4000) , setTimeout("loop()", 4000), 和 setTimeout("loop();", 4000); 都是一樣的, 傳入setTimeout的第一個參數是String.
但是setTimeout(loop(), 4000); 就不同了, loop()是程式, 不是String, 這是直接在loop()中一直執行loop(), 在執行loop(), 再執行loop()......, 而把程式執行的返回地址無限的放入記憶體stack中, 直到stack溢滿了. 而Javascript只能在執行階段檢查出此錯誤語法.

看更多先前的回應...收起先前的回應...
fillano iT邦超人 1 級 ‧ 2013-06-10 18:17:42 檢舉

傳給setTimeout的第一個參數可以是字串或是參考到一個函數的identifier(就當做是變數吧)。如果是字串,setTimeout會用eval來執行,如果是函數,setTimeout會使用像是func.call(this)來執行。(這時由於setTimeout是window物件的方法,所以this是window物件)

fillano iT邦超人 1 級 ‧ 2013-06-10 18:21:10 檢舉

阿,也可以是一個函數。所以,setTimeout('alert("test")', 4000),setTimeout(func, 4000)或是setTimeout(function(){alert('test');}, 4000)這三種方式都可以。

bizpro iT邦大師 1 級 ‧ 2013-06-10 20:15:33 檢舉

謝謝fillano大的回應, 我說的不是很清楚. 再補充囉.
由於javascrip是單線程(Single Threaded)的語言, 所有的呼叫都會儲列(Queued)起來, 在Javascript的遞迴呼叫的迴圈中, 必須要能抵達返回點(exit), 不然就會發生Stack overflow的錯誤了, 在版主的案例中, 唯一的返回點是函式的結尾大括號"}", 並沒有條件exit的地方, 因此, setTimeout的第一個參數如果是直接呼叫函式, 如setTimeout(loop(),4000), 根本就不會執行setTimeout, 而是不斷的呼叫loop()本身, 由於setTimeout從未被呼叫, 當然就無法抵達結尾大括號"}", 就無法結束堆疊起來的前一個呼叫函式loop()了, 相反的, 如果是用傳進String的方式給setTimeout, 如setTimeout("loop()",4000), setTimeout會先被呼叫, 再eval傳進的函式loop(), 這樣前一個呼叫函式loop()就會結束, 而不會一直堆疊在記憶體中.
另外, eval的效能不佳, 有些網站就是因為不斷的eval, 造成網站效能低落, 因此, 在setTimeout裡會直接呼叫函式, 而不是由setTimeout來執行eval, 但是在版主的這個案例中必須犧牲效能用eval.

wllai2001 iT邦新手 5 級 ‧ 2013-06-11 09:50:52 檢舉

非常感謝三位大大的指導,讓在下學習了好多>"<

想再另外請教一下,像是例子中這樣圖片的動畫效果,
如果不用eval的方式,有其他的好方法能不降低效能的達成嗎?

再次感謝三位大大的指導<(__ __)>

wllai2001 iT邦新手 5 級 ‧ 2013-06-11 10:03:34 檢舉

另外,我剛剛在測試一下字串與Function在setTimeout中的差別,
我在程式分別插入了以下兩段程式做測試

&lt;pre class="c" name="code">setTimeout(alert('ok'),4000)

&lt;pre class="c" name="code">setTimeout("alert('ok');",4000)

我發現傳入Function的方式會先暫停4000再執行alert,
但反而傳入字串的方式卻是先alert,而4000就沒有停了,

好像又跟解釋說中的狀況不太一樣耶,不知道我有沒有理解錯誤了QAQ

fillano iT邦超人 1 級 ‧ 2013-06-11 10:52:10 檢舉

我測試是ok的阿XD

以你的例子,這樣的話:

&lt;pre class="c" name="code">
setTimeout(alert('ok'), 4000);

會先執行alert,然後把結果傳給setTimeout。這樣就不會等四秒。

字串的話:

&lt;pre class="c" name="code">
setTimeout("alert('ok')", 4000);

這樣會等四秒才alert。

你第一例應該要改成:

&lt;pre class="c" name="code">
setTimeout(function(){alert('ok')}, 4000);

這樣才是把函數傳給setTimeout。

fillano iT邦超人 1 級 ‧ 2013-06-11 11:39:26 檢舉

我猜你有些觀念不太清楚,跟你解釋一下Javascript函數呼叫的過程。

  1. 由左而右,依序執行每個參數,取得執行結果(立即值結果就是立即值。expression的話則是取得執行結果,例如alert('ok')是一個expression,執行的結果是undefined,所以實際傳給setTimeout的值其實是undefined)
  2. 把1的結果list傳給函數
  3. 函數開始執行,如果函數定義中有定義參數名稱,傳給函數的參數就會設為同名變數的值。如果有定義但是沒傳值,變數的值便是undefined。同時會產生一個arguments物件,內含函數的參數list。

以上是函數開始執行時的過程。

所以甚至可以這樣給參數(跟alert的意思一樣,只是更複雜):

&lt;pre class="c" name="code">
var a = true;
setTimeout(a? function(){alert('a')}:function(){alert('b')}, 4000)
a = false;
setTimeout(a? function(){alert('a')}:function(){alert('b')}, 3000)

結果會先顯示b然後顯示a。

大概這樣...另外,HTML5有定義setTimeout以及setInterval的標準規格(在這之前沒有標準,所以是瀏覽器各自實作),真的很有興趣的話可以看一看:http://www.w3.org/TR/2011/WD-html5-20110525/timers.html

bizpro iT邦大師 1 級 ‧ 2013-06-18 14:25:35 檢舉

?

我要發表回答

立即登入回答