閱讀289 返回首頁    go 阿裏雲 go 技術社區[雲棲]


js中setTimeout和setInterval性能詳解總結

在寫H5遊戲時經常需要使用定時刷新頁麵實現動畫效果,比較常用即setTimeout()以及setInterval()

setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式,而setInterval()則是在每隔指定的毫秒數循環調用函數或表達式,直到clearInterval把它清除。也就是說setTimeout()隻執行一次,setInterval()可以執行多次。兩個函數的參數也相同,第一個參數是要執行的code或句柄,第二個是延遲的毫秒數

setTimeout

描述

var timeoutID = window.setTimeout(code,millisec);

timeoutID:定時器ID號,它可以在clearTimeout()函數中被用來清除定時器。
code:一個被執行的代碼串或函數
millisec:延遲的時間,單位毫秒。如果沒有指定,默認為0

setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式

注:調用過程中,可以使用clearTimeout(id_of_settimeout)終止

window.setTimeout或setTimeout,兩個寫法基本一樣,隻不過window.setTimeout將setTimeout函數作為全局window對象的一個屬性來引用

setTimeout(function timeout(){ 
    console.log(Math.floor(Math.random()*100 + 1)); 
},500)

window.setTimeout方法調用函數有兩種方法:

function hello(){ 
    console.log("hello"); 
} 
window.setTimeout(hello,500);   //不可以有參數
window.setTimeout("hello()",500);   //可以有參數

無論window.setTimeout還是window.setInterval,在使用函數名作為調用句柄時都不能帶參數,而在許多場合必須要帶參數,這就需要想方法解決。例如對於函數hello(_name),它用於針對用戶名顯示歡迎信息: var userName="jack";

function hello(_name){  
    alert("hello,"+_name);   //根據用戶名顯示歡迎信息
 }

這時,如果企圖使用以下語句來使hello函數延遲3秒執行是不可行的:

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

這將使hello函數立即執行,並將返回值作為調用句柄傳遞給setTimeout函數,其結果並不是程序需要的。而使用字符串形式可以達到想要的結果:

window.setTimeout("hello(userName)",3000);

如果在延時期限到達之前取消演示執行,可以使用window.clearTimeout(timeoutId)方法

function hello(){  
    alert("hello");
}
var id=window.setTimeout(hello,5000);
document.onclick=function(){  
    window.clearTimeout(id);
}

這樣,如果要取消顯示,隻需單擊頁麵任何一部分,就執行了window.clearTimeout方法,使得超時操作被取消

除了前兩個參數,setTimeout還允許添加更多的參數。它們將被傳入推遲執行的函數

setTimeout(function(a,b){
  console.log(a+b);
},1000,1,2);  //3

上麵代碼中,setTimeout共有4個參數。最後兩個參數,將在1000毫秒之後回調函數執行時,作為回調函數的參數。

IE 9.0以下版本,隻允許setTimeout有兩個參數。這時有三種解決方法,第一種是自定義setTimeout,使用apply方法將參數輸入回調函數;第二種是在一個匿名函數裏麵,讓回調函數帶參數運行,再把匿名函數輸入setTimeout;第三種使用bind方法,把多餘的參數綁定在回調函數上麵,生成一個新的函數輸入setTimeout

除了參數問題,setTimeout還有一個需要注意的地方:被setTimeout推遲執行的回調函數是在全局環境執行,這有可能不同於函數定義時的上下文環境

var x = 1;
var o = {
    x: 2,
    y: function(){
    console.log(this.x);
  }
};
setTimeout(o.y,1000);  //1

上麵代碼輸出的是1,而不是2,這表示回調函數的運行環境已經變成了全局環境

再看一個不容易發現錯誤的例子

function User(login) {
    this.login = login;
    this.sayHi = function() {
        console.log(this.login);
    }
}
var user = new User('John');
setTimeout(user.sayHi, 1000);  //undefined

上麵代碼隻會顯示undefined,因為等到user.sayHi執行時,它是在全局對象中執行,所以this.login取不到值。為了防止出現這個問題,一種解決方法是將user.sayHi放在函數中執行

setTimeout(function() {
  user.sayHi();
}, 1000);      //John

user.sayHi是在函數作用域內執行,而不是在全局作用域內執行,所以能夠顯示正確的值

另一種更通用的解決方法,則是采用閉包,將this與當前運行環境綁定

document.getElementById('click-ok').onclick = function() {
    var self = this;
    setTimeout(function() { 
        self.value='OK';
    }, 100);
}     //John

上麵代碼中,setTimeout指定的函數中的this,總是指向定義時所在的DOM節點

setInterval

setInterval函數的參數及用法和setTimeout函數一樣,不同的是,setInterval() 方法可按照指定的周期(以毫秒計)來調用函數或計算表達式

var tt = 10; 
function timego(){ 
    tt--; 
    console.log(tt); 
    if(tt==0){ 
        window.location.href='/'; 
        return false; 
    } 
};
window.setInterval("timego()",1000);

我們定義一個定時器timer,使用setInterval()每隔1秒調用一次timego()。這樣timego會執行10次,每次數字tt會減1,直到為0。那麼如果想停止定時器,可以使用以下代碼:

與setTimeout一樣,除了前兩個參數,setInterval方法還可以接受更多的參數,它們會傳入回調函數

function f(){
    for (var i=0;i<arguments.length;i++){
        console.log(arguments[i]);
    }
}
setInterval(f, 1000, "Hello World");
// Hello World
// Hello World
// Hello World
// ...

setInterval指定的是,“開始執行”之間的間隔,因此實際上,兩次執行之間的間隔會小於setInterval指定的時間。假定setInterval指定,每100毫秒執行一次,每次執行需要5毫秒,那麼第一次執行結束後95毫秒,第二次執行就會開始。如果某次執行耗時特別長,比如需要105毫秒,那麼它結束後,下一次執行就會立即開始

var i = 1;
var timer = setInterval(function() { 
  alert(i++);
}, 2000);

上麵代碼每隔2000毫秒跳出一個alert對話框。如果用戶一直不點擊“確定”,整個瀏覽器就處於“堵塞”狀態,後麵的執行就一直無法觸發,會累積起來。舉例來說,第一次跳出alert對話框後,用戶過了6000毫秒才點擊“確定”,那麼第二次、第三次、第四次執行將累積起來,它們之間不會再有等待間隔。

為了確保兩次執行之間有固定的間隔,可以不用setInterval,而是每次執行結束後,使用setTimeout指定下一次執行的具體時間。上麵代碼用setTimeout,可以改寫如下

var i = 1;
var timer = setTimeout(function() {
  alert(i++);
  timer = setTimeout(arguments.callee, 2000); //arguments的主要用途是保存函數參數,這個對象有一個名叫callee的屬性,該屬性是一個指針,指向擁有這個arguments對象的函數
}, 2000);

clearTimeout(對象) 清除已設置的setTimeout對象;clearInterval(對象) 清除已設置的setInterval對象

window.clearInterval(timer);

其實setTimeout()也可以實現每隔一段時間重複執行某個函數,所以在js中setTimeout 和 setInterval 都經常被用來做網頁上的定時器,允許為它指定一個毫秒數作為間隔執行的時間。當被啟動的程序需要在非常短的時間內運行,我們就會給她指定一個很小的時間數,或者需要馬上執行的話,我們甚至把這個毫秒數設置為0,但事實上,setTimeout有一個最小執行時間,當指定的時間小於該時間時,瀏覽器會用最小允許的時間作為setTimeout的時間間隔,也就是說即使我們把setTimeout的毫秒數設置為0,被調用的程序也沒有馬上啟動

setTimeout與setInterval基本原理詳細分析

for(var i = 1; i <= 3; i++) {
    setTimeout(function(){
        console.log(i); // 4 4 4
    },100);
}

很多人會理所當然的認為for循環會分別打印出1,2,3. 但是事實不是這樣的,會輸出3次4. 要理解為什麼會打印三次4,我們先來理解setTimeout這個函數吧,很多人會認為上麵的setTimeout的意思是這樣的:在100毫秒後執行setTimeout的回調函數,其實這樣的理解是有誤的,其實setTimeout與setInterval真正的含義如下:

setTimeout:在指定的毫秒數後,將定時任務處理的函數添加到執行隊列的隊尾

setInterval:按照指定的周期(以毫秒數計時),將定時任務處理函數添加到執行隊列的隊尾

setTimeout與setInterval都是異步的,所以我們現在可以來理解下上麵循環為什麼一直都是4呢?其實調用setTimeout時候,會有一個延時事件排入隊列,然後setTimeout調用之後的那行代碼運行,接著是再下一行代碼,直到再也沒有任何代碼了,javascript虛擬機才會問,隊列裏還有嗎?如果隊列中至少有一個事件適合於觸發,比如上麵的setTimeout函數,則會調用setTimeout那個函數。所以上麵的代碼先for循環,循環結束,而 i === 4一直遞增,直到不再滿足i<=3為止。所以就打印了3個4

setTimeout(function(){
    console.log("打印我,我是異步執行的");
},100);
console.log("我是新來的,我要先執行");

運行結果是:先打印出 “我是新來的,我先執行”這句代碼,接著打印”打印我,我是異步執行的”代碼

setTimeout(f,0)

setTimeout的作用是將代碼推遲到指定時間執行,如果指定時間為0,即setTimeout(f,0),那麼會立刻執行嗎?

答案是不會。因為上一段說過,必須要等到當前腳本的同步任務和“任務隊列”中已有的事件,全部處理完以後,才會執行setTimeout指定的任務。也就是說,setTimeout的真正作用是,在“任務隊列”的現有事件的後麵再添加一個事件,規定在指定時間執行某段代碼。setTimeout添加的事件,會在下一次Event Loop執行

setTimeout(f,0)將第二個參數設為0,作用是讓f在現有的任務(腳本的同步任務和“任務隊列”中已有的事件)一結束就立刻執行。也就是說,setTimeout(f,0)的作用是,盡可能早地執行指定的任務

setTimeout(function() { 
  console.log("Timeout");
}, 0);
function a(x) { 
    console.log("a() 開始運行");
    b(x);
    console.log("a() 結束運行");
}
function b(y) { 
    console.log("b() 開始運行");
    console.log("傳入的值為" + y);
    console.log("b() 結束運行");
}
console.log("當前任務開始");
a(42);
console.log("當前任務結束");
// 當前任務開始
// a() 開始運行
// b() 開始運行
// 傳入的值為42
// b() 結束運行
// a() 結束運行
// 當前任務結束
// Timeout

上麵代碼說明,setTimeout(f,0)必須要等到當前腳本的所有同步任務結束後才會執行。

0毫秒實際上達不到的。根據HTML 5標準,setTimeOut推遲執行的時間,最少是4毫秒。如果小於這個值,會被自動增加到4。另一方麵,瀏覽器內部使用32位帶符號的整數,來儲存推遲執行的時間。這意味著setTimeout最多隻能推遲執行2147483647毫秒(24.8天),超過這個時間會發生溢出,導致回調函數將在當前任務隊列結束後立即執行,即等同於setTimeout(f,0)的效果

應用

可以調整事件的發生順序

比如,網頁開發中,某個事件先發生在子元素,然後冒泡到父元素,即子元素的事件回調函數,會早於父元素的事件回調函數觸發。如果,我們先讓父元素的事件回調函數先發生,就要用到setTimeout(f, 0)

var input = document.getElementsByTagName('input[type=button]')[0];
input.onclick = function A() {
  setTimeout(function B() {
    input.value +=' input'; 
  }, 0)
};
document.body.onclick = function C() {
  input.value += ' body'
};

麵代碼在點擊按鈕後,先觸發回調函數A,然後觸發函數C。在函數A中,setTimeout將函數B推遲到下一輪Loop執行,這樣就起到了,先觸發父元素的回調函數C的目的了

文本框問題

用戶自定義的回調函數,通常在瀏覽器的默認動作之前觸發。比如,用戶在輸入框輸入文本,keypress事件會在瀏覽器接收文本之前觸發。因此,下麵的回調函數是達不到目的的

document.getElementById('input-box').onkeypress = function(event) {
  this.value = this.value.toUpperCase();
}

上麵代碼想在用戶輸入文本後,立即將字符轉為大寫。但是實際上,它隻能將上一個字符轉為大寫,因為瀏覽器此時還沒接收到文本,所以this.value取不到最新輸入的那個字符。隻有用setTimeout改寫,上麵的代碼才能發揮作用

document.getElementById('my-ok').onkeypress = function() {
  var self = this;
  setTimeout(function() {
    self.value = self.value.toUpperCase();
  }, 0);
}

上麵代碼將代碼放入setTimeout之中,就能使得它在瀏覽器接收到文本之後觸發

計算量大、耗時長的任務

由於setTimeout(f,0)實際上意味著,將任務放到瀏覽器最早可得的空閑時段執行,所以那些計算量大、耗時長的任務,常常會被放到幾個小部分,分別放到setTimeout(f,0)裏麵執行

var div = document.getElementsByTagName('div')[0];

// 寫法一
for(var i=0xA00000;i<0xFFFFFF;i++) {
  div.style.backgroundColor = '#'+i.toString(16);
}
// 寫法二
var timer;
var i=0x100000;
function func() {
  timer = setTimeout(func, 0);
  div.style.backgroundColor = '#'+i.toString(16);
  if (i++ == 0xFFFFFF) clearInterval(timer);
}
timer = setTimeout(func, 0);

上麵代碼有兩種寫法,都是改變一個網頁元素的背景色。寫法一會造成瀏覽器“堵塞”,而寫法二就能就不會,這就是setTimeout(f,0)的好處

另一個使用這種技巧的例子是,代碼高亮的處理。如果代碼塊很大,就會分成一個個小塊,寫成諸如setTimeout(highlightNext, 50)的樣子,進行分塊處理

最後更新:2017-06-22 17:01:39

  上一篇:go  java培訓哪些事
  下一篇:go  HTML5響應式模板建站,建站更簡單