首頁  >  文章  >  web前端  >  JavaScript專題之三:防手震

JavaScript專題之三:防手震

coldplay.xixi
coldplay.xixi轉載
2021-03-04 09:56:003161瀏覽

JavaScript專題之三:防手震

目錄

  • #一、為什麼要防手震
  • 二、防手震的原理
  • 三、防手震簡單實作
  • 四、防抖進階
  • 寫在最後

(相關免費學習推薦:javascript影片教學

一、為什麼需要防手震

  • 高頻的函數運算可能會產生不好的影響
  • 如:resize、scroll、mousedown、mousemove、keyup、keydown…

#為此,我們舉個範例程式碼來了解事件如何頻繁的觸發:

我們寫一個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:

JavaScript專題之三:防手震

因為這個範例很簡單,所以瀏覽器完全反應的過來,但假設:

  • 它的觸發頻次極高,1分鐘2000次,且涉及大量的位置計算、DOM 操作等工作,
  • 存在介面請求,單一函數執行時間較長,但每個函數觸發的間隔很近。

這種在一瞬間(短時間內)對瀏覽器或伺服器造成了過多壓力的互動就需要進行最佳化了,為了解決這個問題,一般有兩種解決方案:

  • debounce 防震
  • throttle 節流

他們的目的都是:降低一個函數的觸發頻率,以提高效能或避免資源浪費。

二、防手震的原理

今天重點講到防手震的實作。

防手震的原理是:你儘管觸發事件,但是我一定在事件觸發n秒無操作後才執行。舉個例子:

我們規定3s為防手震的標準,那麼:

  1. 第一次要求執行事件- 此時倒數3s
  2. 倒數2s
  3. 倒數計時1s
  4. 0.5s時事件再次被觸發- 此時倒數3s
  5. …3s內無事可做
  6. #執行事件,共用了5.5s

JavaScript專題之三:防手震

三、自己實作一個防手震

3.1 第一版

我們根據上一節提到的核心思想,實作第一版程式碼:

function debounce(func, wait) {
    var timer;
    return function () {
        clearTimeout(timer)
        timer = setTimeout(func, wait);
    }}

如果我們要使用它,第一節的例子為例:

op.onmousemove = debounce(getUserAction, 2000);

此時大家可以再測試一下,事件持續發生時,只有在完全停止2s後,才會觸發事件:

寫到這裡,作為針對部分高頻事件的需求來說,已經結束了。我們來看看他的效果:

JavaScript專題之三:防手震

3.2 第二版

大家都知道,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事件原有能力的情況下,給函數加上了防手震效果,它可以解決大部分我們日常開發的防手震問題,但我們需要更「完美」

JavaScript專題之三:防手震

四、防手震進階

4.1 立即執行

這個需求就是:

  • #立即執行
  • 維持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);
        }
    }}

再來看下此時他是什麼效果:

JavaScript專題之三:防手震

#4.2 新增簡單驗證

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}

4.3 新增取消事件方法

如果你希望能取消被防手震的事件,我們可以這樣寫:

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}

我們再來看看效果:

JavaScript專題之三:防手震

寫到這裡這個簡單的防手震方法就算OK了,它確實還不算完美,如果在改進上有任何建議,不妨在留言處留言吧~

相關免費學習推薦:javascript#(影片)

以上是JavaScript專題之三:防手震的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除