效能優化方案
1.節流
從滾動條監聽的例子說起:
監聽瀏覽器滾動事件,返回當前滾條與頂部的距離
```
function showTop() {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
console.log("滾動條位置: " + scrollTop);
}
window.onscroll = showTop;
```
在執行的時候會發現存在一個問題:這個函式的預設執行頻率,太!高!了!。 高到什麼程度呢?以chrome為例,我們可以點選選中一個頁面的滾動條,然後點選一次鍵盤的【向下方向鍵】,會發現函式執行了
然而實際上我們並不需要如此高頻的反饋,畢竟瀏覽器的效能是有限的,不應該浪費在這裡,所以接著討論如何優化這種場景。
防抖(debounce)
在第一次觸發事件時,不立即執行函式,而是給出一個期限值,比如200ms,然後,
如果在200ms內沒有再次觸發滾動事件,那麼就執行函式
如果200ms內再次觸發滾動事件,那麼當前的計時取消,重現開始計時
效果:實現如果短時間內觸發統一事件,那麼只會執行一次函式
實現:既然前面都提到了計時,那實現的關鍵就在於setTimeout這個函式,由於還需要一個變數來儲存計時,考慮維護全域性純淨,可以藉助閉包來實現:
```
/**
* fn: function
* delay: number 防抖期限值
*/
function debounce(fn, delay) {
let timer = null; // 藉助閉包
return function () {
if(timer) {
clearTimeout(time);
}
timer = setTimeout(fn, delay);
}
}
window.onscroll = debounce(showTop, 1000);
```
window.onscroll = debounce(showTop, 1000);
此時會發現,必須在停止滾動1秒以後,才會打印出滾動條位置。
到這裡,已經把防抖實現了,現在給出定義:
對於短時間內連續觸發的事件(上面的滾動事件),
防抖的含義就是讓某個時間期限(如上面的1000毫秒)內,事件處理函式只執行一次。
2.節流(throttle)
繼續思考,使用上面的防抖方案來處理問題的結果是:
如果在限定時間段內,不斷觸發滾動事件(比如某個使用者閒著無聊,按住滾動不斷的拖來拖去),只要不停止觸發,理論上就永遠不會輸出當前距離頂部的距離。
但是如果產品同學的期望處理方案是:即使使用者不斷拖動滾動條,也能在某個時間間隔之後給出反饋呢?
我們可以設計一種類似控制閥門一樣定期開放的函式,也就是讓函式執行一次後,在某個時間段內暫時失效,過了這段時間後再重新啟用
效果:如果短時間內大量觸發同一事件,那麼在函式執行一次之後,該函式在指定的時間期限內不再工作,直至過了這段時間才重新生效。
實現 這裡藉助setTimeout來做一個簡單
```/**
* fn: function 需要節流的函式
* delay: number 節流期限值
*/
function throttle(fn, delay) {
let valid = true;
return function () {
if(!valid) {
return false; //時間未到,暫不執行
}
valid = false;
//請注意,節流函式並不止上面這種實現方案,例如可以完全不借助setTimeout
// 可以把狀態位換成時間戳,然後利用時間戳差值是否大於指定間隔時間來做判定。
// 也可以直接將setTimeout的返回的標記當做判斷條件-判斷當前定時器是否存在
// 如果存在表示還在冷卻,並且在執行fn之後消除定時器表示啟用,原理都一樣
setTimeout(() => {
fn();
valid = true;
}, delay);
}
}
```
window.onscroll = throttle(showTop, 200);
執行以上程式碼的結果是:
如果一直拖著滾動條進行滾動,那麼會以1s的時間間隔,持續輸出當前位置和頂部的距離,大大提升網頁遊戲執行速度