js中setTimeout與setInterval之魔法進階篇

編程語言 JavaScript Chrome JSON 大學的那些事情 2017-06-19

共同點

1.javascript的單線程:

function fn() {

setTimeout(function(){alert('can you see me?');},1000); //alert永遠都不會彈出

while(true) {}

}

2.給定時器調用傳遞參數:

無論是window.setTimeout還window.setInterval,在使用函數名作為調用句柄時都不能帶參數,我們可以使用字符串的形式達到想要的結果,如window.setTimeout("hello(userName)",3000);(如果強行使用傳參函數,則會變成立即執行函數);但這種寫法不夠直觀,而且有些場合必須使用函數名,下面用一個小技巧來實現帶參數函數的調用:

<script language="JavaScript" type="text/javascript">

//根據用戶名顯示歡迎信息

function hello(_name){

alert("hello,"+_name);

}

//創建一個函數,用於返回一個無參數函數

function _hello(_name){

return function(){

hello(_name); } }

window.setTimeout(_hello(userName),3000);

</script>

這裡定義了一個函數_hello,用於接收一個參數,並返回一個不帶參數的函數,在這個函數內部使用了外部函數的參數,從而對其調用,不需要使用參數。

異同點

1.setTimeout如果setTimeout後面的代碼執行時間很長,則setTimeout內的代碼要等到setTimeout後面的代碼執行完才能執行,雖然此時很可能已經超過了setTimeout設定的定時時間;

setInterval如果setInterval內代碼執行時間很超過了設定的定時時間,則等待隊列中只能保留一個待執行的程序。

js中setTimeout與setInterval之魔法進階篇

2.如果setTimeout函數的主體部分需要2秒鐘執行完,定時時間為3秒,那麼整個函數則要每5秒鐘才執行一次。

setInterval卻沒有被自己所調用的函數所束縛,它只是簡單地每隔一定時間就重複執行一次那個函數。如果要求在每隔一個固定的時間間隔後就精確地執行某動作,那麼最好使setInterval,而如果不想由於連續調用產生互相干擾的問題,尤其是每次函數的調用需要繁重的計算以及很長的處理時間,那麼最好使用setTimeout。

3. setTimeout遞歸執行的代碼必須是上一次執行完了並間格一定時間才再次執行,比如說: setTimeout延遲時間為1秒執行, 要執行的代碼需要2秒來執行,然後延遲一秒才能執行下一個setTimeout,那這段代碼上一次與下一次的執行時間為3秒. 而不是我們想象的每1秒執行一次。

setInterval是排隊執行的,比如說: setInterval每次延時時間為1秒,而執行的代碼需要2秒執行,那它還是每次去執行這段代碼,上次還沒執行完的代碼會排隊,上一次執行完下一次的就立即執行,就沒有那一秒的延時了,這樣實際執行的間隔時間為2秒。

當定時器遇到閉包

1.總結與研究就是要多做demo,因為有的事情我們看起來很簡單,真正做起來的時候不是那麼一回事。比如如下:

for(var i = 1; i <= 3; i++) {

setTimeout(function(){

console.log(i); //連續輸出3次4

},100);

}

2.第一個參數是用來傳遞要調用的方法,可以傳遞一個代碼串,如下:

1 <script>

2 function fn(value){

3 alert("value=" + value);

4 }

5 setTimeout("fn(1)", 1000);

6 </script>

但是當在一個閉包裡調用的時候,就會出現問題,如:

1 <script>

2 function outerFn(){

3 var value = 1;

4 function fn(){

5 alert("value=" + value);

6 value += 1;

7 }

8 setInterval("fn()", 3000);

9 }

10 outerFn();

11 </script>

會出現錯誤:Uncaught ReferenceError: fn is not defined

原因是fn()是以字符串的方式傳遞的,它的作用域是全局作用域,全局作用域是無法訪問到fn()的。

解決的辦法是fn以函數引用的方式傳遞,也就是setInterval()的第二種傳參方式。

1 <script>

2 function outerFn(){

3 var value = 1;

4 function fn(){

5 alert("value=" + value);

6 value += 1;

7 }

8 setInterval(fn, 3000);

9 }

10 outerFn();

11 </script>

但是這樣又帶來問題,如果想給fn傳參數怎麼辦?可以像如下這樣去寫嗎?

1 <script>

2 function outerFn(){

3 var value = 1;

4 function fn(n){

5 alert("value=" + n);

6 }

7 setTimeout(fn(5), 1000);

8 }

9 outerFn();

10 </script>

答案是不可以的,函數只寫函數名,是函數引用;後面加括號是函數執行。

1 setTimeout(fn, 1000); //fn的引用

2 setTimeout(fn(5), 1000); //fn直接執行

所以第7行,沒有按照預期延遲1000毫秒執行fn(5),而是立刻就執行了。這要注意和上面第一種方式——傳遞代碼字符串的不同。

如果確實有從外部傳參的需要,該怎麼辦呢?

1 <script>

2 function outerFn(value){

3 function fn(){

4 alert("value=" + value);

5 }

6 setTimeout(fn, 1000);

7 }

8 outerFn(5);

9 </script>

如上,是利用了閉包的原理,fn作為內部函數,是可以訪問包含它的outerFn的作用域中的變量的,因此我們想給fn傳參,只要給outerFn傳參就可以了。這在傳遞的參數複雜(比如是一個複雜的json)的情況下,很有用途。

定時器中的this

1.由setTimeout()調用的代碼運行在與所在函數完全分離的執行環境上. 這會導致,這些代碼中包含的 this 關鍵字會指向 window (全局對象)對象,這和所期望的this的值是不一樣的。setInterval的情況類似。

<script type="text/javascript">
 //this指向window
 function shape(name) {
 this.name = name;
 this.timer = function(){alert('my shape is '+this.name)};
 setTimeout(this.timer, 50);
 }
 new shape('rectangle');
</script>

js中setTimeout與setInterval之魔法進階篇

沒有被傳進去,分別用chrome,firefox和IE9實驗了下,都是這個結果

解決方法一:

<script type="text/javascript">
 function shape(name) {
 this.name = name;
 this.timer = function(){alert('my shape is '+this.name)};
 var _this = this;
 setTimeout(function() {_this.timer.call(_this)}, 50);
 }
 new shape('rectangle');
</script>

設置一個局部變量_this,然後放到setTimeout的函數變量中,timer執行call或apply,設置this值。

function能夠調用局部變量_this,多虧了Javascript的閉包。裡面涉及了作用域鏈等知識,有興趣的可以自己去了解下,這裡不展開了

定時器中的事件

setTimeout定時器對隊列的工作方式:當特定時間過去後將代碼添加到隊列中,但並不意味著會馬上將執行,設定一個200ms後執行的定時器,指的是在200ms後它將被添加到隊列中,是否執行,還得看隊列中是否沒有其他的東西。看一下例子:

var a=document.getElementById("nav");
 a.onclick=function(){
 setTimeout(alertsomething,200);
 //一些其他的代碼
}
function alertsomething(){
 alert("it is working");
}

假定onclick處理程序需要執行300ms,這時雖然在205ms添加了定時器代碼,但是仍舊需要等待onclick事件完成後才能夠執行。如圖所示,本來在205ms處添加了定時器代碼,但是由於此時onclick事件還沒結束,故要等到300ms後才執行定時器代碼。

js中setTimeout與setInterval之魔法進階篇

setInterval:為了避免多個定時器代碼不間斷連續運行好幾次,當使用setInterval(),僅當沒有該定時器的任何其他代碼實例時,才將定時器代碼添加到隊列中,通俗點就是等到上個定時器完成,再添加一個。

缺點:

1.某些間隔會被跳過

2.多個定時器的代碼執行之間的間隔可能會比預期小。

js中setTimeout與setInterval之魔法進階篇

在5處,創建一個定時器

205處,添加一個定時器,但是onclick代碼沒執行完成,等待

300處,onclick代碼執行完畢,執行第一個定時器

405處,添加第二個定時器,但前一個定時器沒有執行完成,等待

605處,本來是要添加第三個定時器,但是此時發現,隊列中有了一個定時器,被跳過

等到第一個定時器代碼執行完畢,馬上執行第二個定時器,所以間隔會比預期的小。

解決方法:鏈式調用,主要用於重複定時器

setTimeout(function(){
 //處理代碼
 setTimeout(arguments.callee,interval)
},intercal);

遞歸調用自己。

相關推薦

推薦中...