從新認識定時器

從新認識一

通常,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

參考文章
http://javascript.ruanyifeng....

相關文章
相關標籤/搜索