通常,
setTimeout
函數接受兩個參數,第一個參數func|code是將要推遲執行的函數名或者一段代碼(引擎內部使用eval函數,將字符串轉爲代碼),第二個參數delay是推遲執行的毫秒數。可是,setTimeout
還能夠添加更多參數
。第二個以後的參數都將做爲 推遲執行函數的 參數 傳入。javascript
// 傳入4個參數 setTimeout(function(a,b){ // a=1,b=2 console.log(a+b); },1000,1,2); // 3
IE 9.0及如下版本,只容許setTimeout有兩個參數,不支持更多的參數。有如下解決方法。html
第一種是在一個匿名函數裏面,讓回調函數帶參數運行,再把匿名函數輸入setTimeout。java
setTimeout(function() { myFunc("one", "two", "three"); }, 1000);
上面代碼中,myFunc是真正要推遲執行的函數,有三個參數。若是直接放入setTimeout,低版本的IE不能帶參數,因此能夠放在一個匿名函數。ajax
第二種解決方法是使用bind方法,把多餘的參數綁定在回調函數上面,生成一個新的函數輸入setTimeout。瀏覽器
setTimeout(function(arg1){}.bind(undefined, 10), 1000);
上面代碼中,bind方法第一個參數是undefined,表示將原函數的this綁定全局做用域,第二個參數是要傳入原函數的參數。它運行後會返回一個新函數,該函數不帶參數。服務器
若是被setTimeout推遲執行的
回調函數是某個對象的方法
,那麼該方法中的this
關鍵字將指向全局環境
,而不是定義時所在的那個對象。app
舉例1函數
var x = 1; var o = { x: 2, y: function(){ console.log(this.x); } }; setTimeout(o.y,1000); // 1 // 上面代碼輸出的是1,而不是2,這表示o.y的this所指向的已經不是o,而是全局環境了。
舉例2oop
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放在函數中執行,sayHi是在user做用域內執行,而不是在全局做用域內執行,因此可以顯示正確的值
setTimeout(function() { user.sayHi(); }, 1000);
方法二 使用bind方法,將綁定sayHi綁定在user上面
setTimeout(user.sayHi.bind(user), 1000);
HTML 5標準規定,
setTimeout的最短期間隔是4毫秒。
爲了節電,對於那些不處於當前窗口的頁面,瀏覽器會將時間間隔擴大到1000毫秒
。另外,若是筆記本電腦處於電池供電狀態,Chrome和IE 9以上的版本,會將時間間隔切換到系統定時器,大約是15.6毫秒。setInterval的最短間隔時間是10毫秒
,也就是說,小於10毫秒的時間間隔會被調整到10毫秒。
setInterval函數的用法與setTimeout徹底一致。
setInterval
指定的是「開始執行」之間的間隔,並不考慮每次任務執行自己所消耗的時間。所以實際上,兩次執行之間的間隔會小於指定的時間。好比,setInterval
指定每100ms
執行一次,每次執行須要5ms
,那麼第一次執行結束後95毫秒
,第二次執行就會開始。若是某次執行耗時特別長,好比須要105毫秒,那麼它結束後,下一次執行就會當即開始
。爲了確保兩次執行之間有固定的間隔,能夠不用setInterval,而是每次執行結束後,使用setTimeout指定下一次執行的具體時間。
寫個demo,確保 下一個對話框老是在關閉上一個對話框以後2000毫秒彈出:
var i=1; var timer=setTimeout(function(){ alert(i); timer =setTimeout(arguments.callee,2000); },2000)
用setTimeout模擬了setInterval
function interval(func,wait){ var interv=function(){ func.call(); setTimeout(interv,wait); } setTimeout(interv,wait); } interval(function(){console.log(1)},1000)
setTimeout和setInterval返回的整數值是連續的,也就是說,第二個setTimeout方法返回的整數值,將比第一個的整數值大1。利用這一點,能夠寫一個函數,取消當前全部的setTimeout。
clearTimeout實際應用的例子。有些網站會實時將用戶在文本框的輸入,經過Ajax方法傳回服務器,jQuery的寫法以下。
$('textarea').on('keydown', ajaxAction);
這樣寫有一個很大的缺點,就是若是用戶連續擊鍵,就會連續觸發keydown事件,形成大量的Ajax通訊。這是沒必要要的,並且極可能會發生性能問題。正確的作法應該是,設置一個門檻值,表示兩次Ajax通訊的最小間隔時間。若是在設定的時間內,發生新的keydown事件,則不觸發Ajax通訊,而且從新開始計時。若是過了指定時間,沒有發生新的keydown事件,將進行Ajax通訊將數據發送出去。
debounce 防抖動方法
function debounce(fn, delay){ var timer = null; // 聲明計時器 return function(){ //保存當前做用域的 this和 arguments var context = this; var args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, delay); }; } // 用法示例 $('textarea').on('keydown', debounce(ajaxAction, 2500))
setTimeout和setInterval的運行機制是,將
指定的代碼移出本次執行
,等到下一輪Event Loop
時,再檢查是否到了指定時間。若是到了,就執行對應的代碼;若是不到,就等到再下一輪Event Loop時從新判斷。這意味着,
setTimeout和setInterval指定的代碼,必須等到本輪Event Loop的全部同步任務都執行完,再等到本輪Event Loop的「任務隊列」的全部任務執行完,纔會開始執行
。因爲前面的任務到底須要多少時間執行完,是不肯定的,因此沒有辦法保證,setTimeout和setInterval指定的任務,必定會按照預約時間執行。
setIntervel具備累積效應
,若是某個操做特別耗時,超過了setInterval的時間間隔,排在後面的操做會被累積起來,而後在很短的時間內連續觸發
,這可能或形成性能問題(好比集中發出Ajax請求)。
setInterval(function () { console.log(2); }, 1000); (function () { sleeping(3000); })(); // 2,2,2 // 2 // ... 結果就是等到第二行語句運行完成之後,馬上連續輸出三個2,而後開始每隔1000毫秒,輸出一個2。
等到當前腳本的同步任務和「任務隊列」中已有的事件,所有處理完之後,纔會執行
setTimeout
指定的任務。也就是說,setTimeout的真正做用是,在「消息隊列」的現有消息的後面再添加一個消息,規定在指定時間執行某段代碼。setTimeout添加的事件,會在下一次Event Loop執行。
setTimeout(f, 0)
將第二個參數設爲0,做用是讓f在現有的任務(腳本的同步任務和「消息隊列」指定的任務)一結束就馬上執行。也就是說,setTimeout(f, 0)的做用是,儘量早地執行指定的任務。而並非會馬上就執行這個任務。
setTimeout(f, 0)指定的任務,最先也要到下一次Event Loop纔會執行
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
能夠調整事件的發生順序。
例子1
網頁開發中,某個事件先發生在子元素,而後冒泡到父元素,即子元素的事件回調函數,會早於父元素的事件回調函數觸發。若是,咱們先讓父元素的事件回調函數先發生,就要用到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的目的了。
例子2
用戶在輸入框輸入文本,keypress事件會在瀏覽器接收文本以前觸發。想在用戶輸入文本後,當即將字符轉爲大寫。可是實際上,它只能將上一個字符轉爲大寫,由於瀏覽器此時還沒接收到文本。
document.getElementById('my-ok').onkeypress = function() { var self = this; setTimeout(function() { self.value = self.value.toUpperCase(); }, 0); }
例子3
var div = document.getElementsByTagName('div')[0]; // 寫法一 形成瀏覽器「堵塞」,由於JavaScript執行速度遠高於DOM,會形成大量DOM操做「堆積」 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);
正常任務(task)與微任務(microtask)。它們的區別在於,「正常任務」在下一輪Event Loop執行,「微任務」在本輪Event Loop的全部任務結束後執行。
正常任務:
setTimeout
setInterval
setImmediate
I/O
各類事件(好比鼠標單擊事件)的回調函數
微任務:
process.nextTick
Promise
console.log(1); setTimeout(function() { console.log(2); }, 0); Promise.resolve().then(function() { console.log(3); }).then(function() { console.log(4); }); console.log(5); // 1 // 5 // 3 // 4 // 2