(JS基礎)定時器高級用法

setTimeout()

建立定時器,程序會被掛起,時間結束時把任務添加到 JavaScript 進程棧中,又由於 JavaScript 是單線程,必須等待前面的代碼執行結束,因此定時器執行函數總會比設定時間要晚。
javascript

基本用法:html

setTimeout(() => {
  // 處理代碼 ...
}, timeout);複製代碼


setInterval()

與 "setTimeout" 相似,區別在於 "setInterval" 會按期向 JavaScript 進程棧添加任務,但會有幾個問題發生。
java

(1) 某些間隔會被跳過;
編程

(2) 多個定時器的代碼執行之間的間隔可能比設定值要小。
數組

(3) 前一個定時器代碼未執行,當前定時器代碼則被跳過。
瀏覽器

先介紹基本用法:閉包

setInterval(() => {
  // 每隔一段時間執行 ...
}, interval);複製代碼

引用《JavaScript高級編程》的一個圖:函數

"onclick"時間處理函數內添加一個 "setInterval" 重複定時器,且給定了 200ms 的間隔,而定時器代碼須要 300ms+ 才能執行完,但 "onclick" 處理函數內還有其餘代碼須要執行,耗時 300ms。動畫

那麼第一個定時器代碼執行與 300ms 處,第二個則執行與 600ms+ 處,而 605ms 處本該被添加的定時器則被跳過,由於第二個定時器代碼被添加卻未被執行。ui


使用遞歸 "setTimeout" 代替 "setInterval"

代碼以下:

setTimeout(() => {
  // 處理代碼 ....
  setTimeout(arguments.callee, interval);
}, interval);複製代碼

但上述代碼存在必定問題,由於嚴格模式下,沒法使用 "arguments.callee" 得到函數自己。建議把處理函數具名化,以下:

setTimeout(function process() {
  // 處理代碼 ...
  setTimeout(process, 1000);
}, 1000);複製代碼


定時器妙用

Yielding Processes

運行在瀏覽器中的 JavaScript 都被分配了一個肯定數量的資源。若是代碼運行超過特定時間或者特定語句數量就不會繼續執行。過長、嵌套過深的函數調用或者是進行大量處理的循環都是形成腳本時間運行過長的主要緣由。

如如下代碼,假設 "process" 處理須要時間過長,數組長度也較長,該函數則會阻塞進程。
for (let i = 0, len = data.length; i < len; i++) {
  // 假設處理須要 200ms
  process(data[i])
}複製代碼

要改善上述狀況,先要符合兩個如下條件,則能夠使用 "數組分塊" 技術:

  • 該處理不須要同步完成;
  • 數據能夠不按順序完成。
setTimeout(function timeoutProcess() {
  // 取出下一個條目並處理,data:<Array>
  let item = data.shift();
  process(item);
  // 判斷是否還有條目,有則設置另外一個定時器
  if (arr.length > 0) {
    setTimeout(timeoutProcess, 100);
  }
}, 100);複製代碼


函數防抖(debounce)

某些高頻操做是不必的,如用戶連續點擊某個開關,致使請求屢次發送。則能夠使用 "函數防抖" ,等待用戶操做停下後片刻再發送請求。

"函數防抖" 的原理是利用定時器延遲執行,當重複執行時,先把本來定時器清除,再添加延遲執行代碼,那麼老是最後一次操做後延時執行代碼。

核心代碼

// 防抖函數,兩個參數:被防抖的函數,函數執行的上下文(忽略則爲全局)
function debounce(methods, context){
  clearTimeout(methods.timerId);
  // 對方法添加一個屬性 timerId ,存放定時器
  methods.timerId = setTimeout(() => {
    methods.call(context);
  }, 100);
}複製代碼

完整實例:(以 click 事件爲例)

// <button id="btn">test</button> // 按鈕
// 代碼:
function debounce(fn, delay) {
  // 經過閉包形式,與定時器對象關聯
  let timer = null;
  return function () {
    // 將參數和上下文傳遞給實際執行的函數
    let context = this,
      args = arguments;
    if (timer) {
      clearTimeout(timer);
      timer = null
    }
    timer = setTimeout(() => {
      fn.call(this, args, '給防抖函數的額外參數')
    }, delay);
  }
}
// 實際處理函數,以打印爲例
function printFn(args, myArgs) {
  console.log('do it!', ...args, myArgs)
}
// 以click事件爲例,只有最後一次單擊才生效
document.getElementById('btn').addEventListener('click', debounce(printFn, 1000))
複製代碼

函數節流(throttle)

當咱們以必定頻率處理某些頻發事件,咱們能夠使用"函數節流"。

"函數節流" 的原理是利用定時器延遲執行,當重複執行時,忽略新添加的處理函數。

核心代碼

// 節流函數,兩個參數:被節流的函數,函數執行的上下文(忽略則爲全局)
function throttle(methods, context) {
  // 函數不含 timerId 時添加定時器對象,定時器對象存在期間不重複添加
  if (!methods.timerId) {
    methods.timerId = setTimeout(() => {
      methods.timerId = null;
      methods.call(context);
    }, 1000);
  }
}
複製代碼

完整實例:(以 click 事件爲例)

// <button id="btn">test</button> // 按鈕
// 代碼:
function throttle(fn, delay) {
  // 經過閉包形式,與定時器對象關聯
  let timer = null;
  return function () {
    let context = this,
      args = arguments;
    // 當定時器存在時,則不操做,不然添加延時函數
    if (!timer) {
      timer = setTimeout(() => {
        timer = null
        fn.call(this, args, '給防抖函數的額外參數')
      }, delay);
    }
  }
}
// 實際處理函數,以打印爲例
function printFn(args, myArgs) {
  console.log('do it!', ...args, myArgs)
}
// 以click事件爲例,連續點擊的狀況下,每隔一秒生效一次
document.getElementById('btn').addEventListener('click', throttle(printFn, 1000))複製代碼


window.requestAnimationFrame()

requestAnimationFrame是HTML5新增的定時器。用法與setTimeout相似,都是在一段時間後執行回調函數,區別在於不用傳第二個時間參數。

執行回調函數的時延是根據顯示器刷新率決定的,例如刷新率爲 60Hz 的顯示器,時延爲1000ms/60,約等於16.6ms。執行回調函數的時間是比較準確的(衆所周知,setTimeoutsetInterval的執行回調函數時間並不許確)。

使用cancelAnimationFrame()方法能夠取消requestAnimationFrame()定時器。

引用其餘博主的一個例子:(進度條動畫)

<div id="myDiv" style="background:lightblue;width:0;height:20px;line-height:20px;">0%</div> <button id="btn">run</button>複製代碼
let btn = document.getElementById('btn'),
  myDiv = document.getElementById('myDiv'),
  timer;
btn.onclick = function () {
  myDiv.style.width = '0'; cancelAnimationFrame(timer);
  timer = requestAnimationFrame(function fn() {
    if (parseInt(myDiv.style.width) < 500) {
      myDiv.style.width = parseInt(myDiv.style.width) + 5 + 'px';
      myDiv.innerHTML = parseInt(myDiv.style.width) / 5 + '%';
      timer = requestAnimationFrame(fn);
    } else {
      cancelAnimationFrame(timer);
    }
  });
} 複製代碼
相關文章
相關標籤/搜索