目錄
寫在最後
(相關免費學習推薦:javascript影片教學)
一、為什麼需要防手震
#為此,我們舉個範例程式碼來了解事件如何頻繁的觸發:
我們寫一個index.html 檔案:
<meta> <meta> <meta> <title>Document</title> <title>debounce</title> <style> #wrapper { width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px; } </style> <p></p> <script> var count = 1; var op = document.getElementById("wrapper"); function getUserAction() { op.innerHTML = count++; } op.onmousemove = getUserAction; </script>
從左邊滑到右邊就觸發了近100次getUserAction
函數!看如下Gif:
因為這個範例很簡單,所以瀏覽器完全反應的過來,但假設:
這種在一瞬間(短時間內)對瀏覽器或伺服器造成了過多壓力的互動就需要進行最佳化了,為了解決這個問題,一般有兩種解決方案:
他們的目的都是:降低一個函數的觸發頻率,以提高效能或避免資源浪費。
二、防手震的原理
今天重點講到防手震的實作。
防手震的原理是:你儘管觸發事件,但是我一定在事件觸發n秒無操作後
才執行。舉個例子:
我們規定3s
為防手震的標準,那麼:
三、自己實作一個防手震
我們根據上一節提到的核心思想,實作第一版程式碼:
function debounce(func, wait) { var timer; return function () { clearTimeout(timer) timer = setTimeout(func, wait); }}
如果我們要使用它,第一節的例子為例:
op.onmousemove = debounce(getUserAction, 2000);
此時大家可以再測試一下,事件持續發生時,只有在完全停止2s後,才會觸發事件:
寫到這裡,作為針對部分高頻事件的需求來說,已經結束了。我們來看看他的效果:
大家都知道,dom節點在觸發事件的時候,this指向它本身,本例中則指向op
,但在本例中:我們看一下
var count = 1;var op = document.getElementById("op");function getUserAction() { op.innerHTML = count++; console.log('this', this); // 此时输出 Window...}op.onmousemove = debounce(getUserAction, 2000);function debounce(func, wait) { var timer; return function () { clearTimeout(timer) timer = setTimeout(func, wait); }}
畢竟經過了一層匿名函數的包裹,this已經指向了window,為了減少影響,我們嘗試修正它
function debounce(func, wait) { var timer; return function () { var _this = this; // 记录当前this clearTimeout(timer) timer = setTimeout(function(){ func.apply(_this); //将 func的this改为_this }, wait); }}
解決的this指向問題,我們的函數仍然不夠“完美”,JavaScript中,事件處理函數會提供event
對象,我們簡稱為e。
// 使用了 debouce 函数function getUserAction(e) { console.log(e); // undefined op.innerHTML = count++;};
為了確保它的原汁原味,我們再改第三版:
var count = 1;var op = document.getElementById("op");function getUserAction(e) { op.innerHTML = count++; console.log('e', e); // MouseEvent}op.onmousemove = debounce(getUserAction, 2000);function debounce(func, wait) { var timer; return function () { var _this = this; // 记录当前this var arg = arguments; // 记录参数 clearTimeout(timer) timer = setTimeout(function () { func.apply(_this, arg); //将 func的this改为_this }, wait); }}
到此為止,我們在盡可能保留Dom事件原有能力的情況下,給函數加上了防手震效果,它可以解決大部分我們日常開發的防手震問題,但我們需要更「完美」
四、防手震進階
這個需求就是:
n
秒空白期n
秒空白期置後想想這個需求也是很有道理的嘛,那我們加個immediate
參數判斷是否是立刻執行。
function debounce(func, wait, immediate) { var timer; return function () { var _this = this; var args = arguments; if (timer) clearTimeout(timer); // 常规流程,间隔内触发时清掉重置定时 if (immediate) { // 如果已经执行过,不再执行 var callNow = !timer; // 1. callNow 初始值是 true, 同步立即执行;随后 timer 才开始执行 timer = setTimeout(function(){ timer = null; // wait 期间,timer 是一个 ID 数字,所以 callNow 为 false,func 在此期间永远不会执行 }, wait) // wait 之后,timer 赋值 null,callNow 为 true,func 又开始立即执行。 if (callNow) func.apply(_this, args) } else { timer = setTimeout(function(){ func.apply(_this, args) }, wait); } }}
再來看下此時他是什麼效果:
function debounce(func, wait, immediate) { var timer; // 检查函数 if (typeof func !== 'function') { throw new TypeError('Expected a function'); } // 保证wait存在 wait = +wait || 0; const debounced = function () { var _this = this; var args = arguments; if (timer) clearTimeout(timer); // 常规流程,间隔内触发时清掉重置定时 if (immediate) { // 如果已经执行过,不再执行 var callNow = !timer; // 如果不存在定时器,则callNow为true timer = setTimeout(function () { timer = null; // 为了保证之后的时效性,手动添加timer }, wait) // 因为不存在timer,证明是首次执行,所以直接调用 if (callNow) func.apply(_this, args) } else { timer = setTimeout(function () { func.apply(_this, args) }, wait); } } return debounced}
如果你希望能取消被防手震的事件,我們可以這樣寫:
function debounce(func, wait, immediate) { var timer; // 检查函数 if (typeof func !== 'function') { throw new TypeError('Expected a function'); } // 保证wait存在 wait = +wait || 0; const debounced = function () { var _this = this; var args = arguments; if (timer) clearTimeout(timer); // 常规流程,间隔内触发时清掉重置定时 if (immediate) { // 如果已经执行过,不再执行 var callNow = !timer; // 如果不存在定时器,则callNow为true timer = setTimeout(function () { timer = null; // 为了保证之后的时效性,手动添加timer }, wait) // 因为不存在timer,证明是首次执行,所以直接调用 if (callNow) func.apply(_this, args) } else { timer = setTimeout(function () { func.apply(_this, args) }, wait); } } const cancel = function(){ clearTimeout(timer); timer = null; } const pending = function(){ return timer !== undefined; } debounced.cancel = cancel; debounced.pending = pending; return debounced}
我們再來看看效果:
寫到這裡這個簡單的防手震方法就算OK了,它確實還不算完美,如果在改進上有任何建議,不妨在留言處留言吧~
相關免費學習推薦:javascript#(影片)
以上是JavaScript專題之三:防手震的詳細內容。更多資訊請關注PHP中文網其他相關文章!