JavaScript 提供定時執行代碼的功能,叫作定時器(timer),主要由setTimeout()和setInterval()這兩個函數來完成。它們向任務隊列添加定時任務ajax
setTimeout()
setInterval()
clearTimeout(),clearInterval()
實例:debounce 函數
運行機制
setTimeout(f, 0)
含義
應用瀏覽器
1.setTimeout()
執行多少毫秒後執行,返回一個編號(順序遞增),用於取消。
第一個參數func|code是將要推遲執行的函數名或者一段代碼,第二個參數delay是推遲執行的毫秒數。後面的參數爲傳給回調函數的參數
setTimeout(function (a,b) {
console.log(a + b);
}, 1000, 1, 1);服務器
This(回調函數爲對象方法)爲全局
var x = 1;app
var obj = {
x: 2,
y: function () {函數
console.log(this.x);
}
};性能
setTimeout(obj.y, 1000) // 1
上面代碼輸出的是1,而不是2。由於當obj.y在1000毫秒後運行時,this所指向的已經不是obj了,而是全局環境。動畫
爲了防止出現這個問題,一種解決方法是將obj.y放入一個函數。this
var x = 1;code
var obj = {
x: 2,
y: function () {對象
console.log(this.x);
}
};
setTimeout(function () {
obj.y();
}, 1000);
// 2
上面代碼中,obj.y放在一個匿名函數之中,這使得obj.y在obj的做用域執行,而不是在全局做用域內執行,因此可以顯示正確的值。
另外一種解決方法是,使用bind方法,將obj.y這個方法綁定在obj上面。
var x = 1;
var obj = {
x: 2,
y: function () {
console.log(this.x);
}
};
setTimeout(obj.y.bind(obj), 1000)
//
2.setInterval()
setInterval指定某個任務每隔一段時間就執行一次,也就是無限次的定時執行。
下面是一個經過setInterval方法實現網頁動畫的例子。
var div = document.getElementById('someDiv');
var opacity = 1;
var fader = setInterval(function() {
opacity -= 0.1;
if (opacity >= 0) {
div.style.opacity = opacity;
} else {
clearInterval(fader);
}
}, 100);
上面代碼每隔100毫秒,設置一次div元素的透明度,直至其徹底透明爲止。
setInterval的一個常見用途是實現輪詢。下面是一個輪詢 URL 的 Hash 值是否發生變化的例子。
var hash = window.location.hash;
var hashWatcher = setInterval(function() {
if (window.location.hash != hash) {
updatePage();
}
}, 1000);
時間
不考慮執行時間即會小於100ms ,第二次執行就會開始。若是某次執行耗時特別長,好比須要105毫秒,那麼它結束後,下一次執行就會當即開始。
爲了確保兩次執行之間有固定的間隔,能夠不用setInterval,而是每次執行結束後,使用setTimeout指定下一次執行的具體時間。
var i = 1;
var timer = setTimeout(function f() {
// ...
timer = setTimeout(f, 2000);
}, 2000);
上面代碼能夠確保,下一次執行老是在本次執行結束以後的2000毫秒開始。
3.clearTimeout(),clearInterval()
利用這一點,能夠寫一個函數,取消當前全部的setTimeout定時器。
(function() {
// 每輪事件循環檢查一次
var gid = setInterval(clearAllTimeouts, 0);
function clearAllTimeouts() {
var id = setTimeout(function() {}, 0); while (id > 0) { if (id !== gid) { clearTimeout(id); } id--; }
}
})();
上面代碼中,先調用setTimeout,獲得一個計算器編號,而後把編號比它小的計數器所有取消
4.實例:debounce 函數
debounce(防抖動)監聽時間時(Keypress,一直觸發函數。
有時,咱們不但願回調函數被頻繁調用。好比,用戶填入網頁輸入框的內容,但願經過 Ajax 方法傳回服務器,jQuery 的寫法以下。
$('textarea').on('keydown', ajaxAction);
$('textarea').on('keydown', debounce(ajaxAction, 2500));
function debounce(fn, delay){
var timer = null; // 聲明計時器
return function() {
var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay);
};
}
上面代碼中,只要在2500毫秒以內,用戶再次擊鍵,就會取消上一次的定時器,而後再新建一個定時器。這樣就保證了回調函數之間的調用間隔,至少是2500毫秒。
5.運行機制
將指定的代碼移到下一輪事件循環,等這輪輪完,再檢查是否到了指定時間。若是到了,就執行對應的代碼;若是不到,就繼續等待。
回調函數必須等本輪運行完,所以時間不肯定。
setTimeout(someTask, 100);
veryLongTask();
上面代碼的setTimeout,指定100毫秒之後運行一個任務。可是,若是後面的veryLongTask函數(同步任務)運行時間很是長,過了100毫秒還沒法結束,那麼被推遲運行的someTask就只有等着,等到veryLongTask運行結束,才輪到它執行
6.setTimeout(f, 0)
6.1含義
由於上一節說過,必需要等到當前腳本的同步任務,所有處理完之後,纔會執行setTimeout指定的回調函數f
setTimeout(function () {
console.log(1);
}, 0);
console.log(2);
// 2
// 1
上面代碼先輸出2,再輸出1。由於2是同步任務,在本輪事件循環執行,而1是下一輪事件循環執行。
setTimeout(f, 0)會在下一輪事件循環一開始就執行
6.2應用
6.2.1它的一大應用是,能夠調整事件的發生順序。
好比,網頁開發中,某個事件先發生在子元素,而後冒泡到父元素,即子元素的事件回調函數,會早於父元素的事件回調函數觸發。若是,想讓父元素的事件回調函數先發生,就要用到setTimeout(f, 0)。
// HTML 代碼以下
// <input type="button" id="myButton" value="click">
var input = document.getElementById('myButton');
input.onclick = function A() {
setTimeout(function B() {
input.value +=' input';
}, 0)
};
document.body.onclick = function C() {
input.value += ' body'
};
上面代碼在點擊按鈕後,先觸發回調函數A,而後觸發函數C。函數A中,setTimeout將函數B推遲到下一輪事件循環執行,這樣就起到了,先觸發父元素的回調函數C的目的了。
6.2.2另外一個應用是,用戶自定義的回調函數,一般在瀏覽器的默認動做以前觸發。
好比,用戶在輸入框輸入文本,keypress事件會在瀏覽器接收文本以前觸發。所以,下面的回調函數是達不到目的的。
// HTML 代碼以下
// <input type="text" id="input-box">
document.getElementById('input-box').onkeypress = function (event) {
this.value = this.value.toUpperCase();
}
上面代碼想在用戶每次輸入文本後,當即將字符轉爲大寫。可是實際上,它只能將本次輸入前的字符轉爲大寫,由於瀏覽器此時還沒接收到新的文本,因此this.value取不到最新輸入的那個字符。只有用setTimeout改寫,上面的代碼才能發揮做用。
document.getElementById('input-box').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) clearTimeout(timer);
}
timer = setTimeout(func, 0);
上面代碼有兩種寫法,都是改變一個網頁元素的背景色。寫法一會形成瀏覽器「堵塞」,由於 JavaScript 執行速度遠高於 DOM,會形成大量 DOM 操做「堆積」,而寫法二就不會,這就是setTimeout(f, 0)的好處。
6.2.3另外一個使用這種技巧的例子是代碼高亮的處理若是代碼塊很大,一次性處理,可能會對性能形成很大的壓力,那麼將其分紅一個個小塊,一次處理一塊,好比寫成setTimeout(highlightNext, 50)的樣子,性能壓力就會減輕