防抖模式

防抖和節(jié)流的區(qū)別

防抖

觸發(fā)高頻事件后n秒內函數(shù)只會執(zhí)行一次疑故,如果n秒內高頻事件再次被觸發(fā),則重新計算時間

  • 實現(xiàn)方式:每次觸發(fā)事件時設置一個延遲調用方法易猫,并且取消之前的延時調用方法
  • 缺點:如果事件在規(guī)定的時間間隔內被不斷的觸發(fā)趣苏,則調用方法會被不斷的延遲

節(jié)流

  • 實現(xiàn)方式:每次觸發(fā)事件時,如果當前有等待執(zhí)行的延時函數(shù)凫海,則直接return

前言

在前端開發(fā)中會遇到一些頻繁的事件觸發(fā),比如:

  • window 的 resize男娄、scroll
  • mousedown、mousemove
  • keyup、keydown
    ……

為此模闲,我們舉個示例代碼來了解事件如何頻繁的觸發(fā):
我們寫個 index.html 文件:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <script src="debounce.js"></script>
</body>
</html>

debounce.js 文件的代碼如下:

var count = 1;
var container = document.getElementById('container');

function getUserAction() {
    container.innerHTML = count++;
};

container.onmousemove = getUserAction;

我們來看看效果:


從左邊滑到右邊就觸發(fā)了 165 次 getUserAction 函數(shù)建瘫!

因為這個例子很簡單,所以瀏覽器完全反應的過來尸折,可是如果是復雜的回調函數(shù)或是 ajax 請求呢啰脚?假設 1 秒觸發(fā)了 60 次,每個回調就必須在 1000 / 60 = 16.67ms 內完成实夹,否則就會有卡頓出現(xiàn)橄浓。

為了解決這個問題,一般有兩種解決方案:

  • debounce 防抖
  • throttle 節(jié)流

防抖

今天重點講講防抖的實現(xiàn)亮航。

防抖的原理就是:你盡管觸發(fā)事件荸实,但是我一定在事件觸發(fā) n 秒后才執(zhí)行,如果你在一個事件觸發(fā)的 n 秒內又觸發(fā)了這個事件缴淋,那我就以新的事件的時間為準准给,n 秒后才執(zhí)行,總之重抖,就是要等你觸發(fā)完事件 n 秒內不再觸發(fā)事件露氮,我才執(zhí)行,真是任性吶!

第一版

根據(jù)這段表述钟沛,我們可以寫第一版的代碼:

// 第一版
function debounce(func, wait) {
    var timeout;
    return function () {
        clearTimeout(timeout)
        timeout = setTimeout(func, wait);
    }
}

如果我們要使用它畔规,以最一開始的例子為例:

container.onmousemove = debounce(getUserAction, 1000);

現(xiàn)在隨你怎么移動,反正你移動完 1000ms 內不再觸發(fā)恨统,我才執(zhí)行事件叁扫。看看使用效果:

頓時就從 165 次降低成了 1 次!

this

如果我們在 getUserAction 函數(shù)中 console.log(this)延欠,在不使用 debounce 函數(shù)的時候陌兑,this 的值為:

<div id="container"></div>

但是如果使用我們的 debounce 函數(shù),this 就會指向 Window 對象由捎!

所以我們需要將 this 指向正確的對象兔综。

我們修改下代碼:

// 第二版
function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this;
        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context)
        }, wait);
    }
}

現(xiàn)在 this 已經(jīng)可以正確指向了。讓我們看下個問題:

event 對象

JavaScript 在事件處理函數(shù)中會提供事件對象 event狞玛,我們修改下 getUserAction 函數(shù):

function getUserAction(e) {
    console.log(e);
    container.innerHTML = count++;
};

如果我們不使用 debouce 函數(shù)软驰,這里會打印 MouseEvent 對象,如圖所示:

但是在我們實現(xiàn)的 debounce 函數(shù)中心肪,卻只會打印 undefined!

所以我們再修改一下代碼:

// 第三版
function debounce(func, wait) {
    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        clearTimeout(timeout)
        timeout = setTimeout(function(){
            func.apply(context, args)
        }, wait);
    }
}

到此為止锭亏,我們修復了兩個小問題:

  1. this 指向
  2. event 對象

立刻執(zhí)行

這個時候,代碼已經(jīng)很是完善了硬鞍,但是為了讓這個函數(shù)更加完善慧瘤,我們接下來思考一個新的需求戴已。

這個需求就是:

我不希望非要等到事件停止觸發(fā)后才執(zhí)行,我希望立刻執(zhí)行函數(shù)锅减,然后等到停止觸發(fā) n 秒后糖儡,才可以重新觸發(fā)執(zhí)行。

想想這個需求也是很有道理的嘛怔匣,那我們加個 immediate 參數(shù)判斷是否是立刻執(zhí)行握联。

// 第四版
function debounce(func, wait, immediate) {

    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已經(jīng)執(zhí)行過,不再執(zhí)行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}

再來看看使用效果:

返回值

此時注意一點每瞒,就是 getUserAction 函數(shù)可能是有返回值的金闽,所以我們也要返回函數(shù)的執(zhí)行結果,但是當 immediate 為 false 的時候剿骨,因為使用了 setTimeout 代芜,我們將 func.apply(context, args) 的返回值賦給變量,最后再 return 的時候懦砂,值將會一直是 undefined蜒犯,所以我們只在 immediate 為 true 的時候返回函數(shù)的執(zhí)行結果。

// 第五版
function debounce(func, wait, immediate) {

    var timeout, result;

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已經(jīng)執(zhí)行過荞膘,不再執(zhí)行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    }
}

取消

最后我們再思考一個小需求罚随,我希望能取消 debounce 函數(shù),比如說我 debounce 的時間間隔是 10 秒鐘羽资,immediate 為 true淘菩,這樣的話,我只有等 10 秒后才能重新觸發(fā)事件屠升,現(xiàn)在我希望有一個按鈕潮改,點擊后,取消防抖腹暖,這樣我再去觸發(fā)汇在,就可以又立刻執(zhí)行啦,是不是很開心脏答?

為了這個需求糕殉,我們寫最后一版的代碼:

// 第六版
function debounce(func, wait, immediate) {

    var timeout, result;

    var debounced = function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 如果已經(jīng)執(zhí)行過,不再執(zhí)行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) result = func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
        return result;
    };

    debounced.cancel = function() {
        clearTimeout(timeout);
        timeout = null;
    };

    return debounced;
}

那么該如何使用這個 cancel 函數(shù)呢殖告?依然是以上面的 demo 為例:

var count = 1;
var container = document.getElementById('container');

function getUserAction(e) {
    container.innerHTML = count++;
};

var setUseAction = debounce(getUserAction, 10000, true);

container.onmousemove = setUseAction;

document.getElementById("button").addEventListener('click', function(){
    setUseAction.cancel();
})

演示效果如下:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末阿蝶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子黄绩,更是在濱河造成了極大的恐慌羡洁,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爽丹,死亡現(xiàn)場離奇詭異筑煮,居然都是意外死亡辛蚊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門咆瘟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚼隘,“玉大人,你說我怎么就攤上這事袒餐。” “怎么了谤狡?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵灸眼,是天一觀的道長。 經(jīng)常有香客問我墓懂,道長焰宣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任捕仔,我火速辦了婚禮匕积,結果婚禮上,老公的妹妹穿的比我還像新娘榜跌。我一直安慰自己闪唆,他們只是感情好,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布钓葫。 她就那樣靜靜地躺著悄蕾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪础浮。 梳的紋絲不亂的頭發(fā)上帆调,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機與錄音豆同,去河邊找鬼番刊。 笑死,一個胖子當著我的面吹牛影锈,可吹牛的內容都是我干的芹务。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼精居,長吁一口氣:“原來是場噩夢啊……” “哼锄禽!你這毒婦竟也來了?” 一聲冷哼從身側響起靴姿,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沃但,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后佛吓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宵晚,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡垂攘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了淤刃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晒他。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逸贾,靈堂內的尸體忽然破棺而出陨仅,到底是詐尸還是另有隱情,我是刑警寧澤铝侵,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布灼伤,位于F島的核電站,受9級特大地震影響咪鲜,放射性物質發(fā)生泄漏狐赡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一疟丙、第九天 我趴在偏房一處隱蔽的房頂上張望颖侄。 院中可真熱鬧,春花似錦享郊、人聲如沸览祖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穴墅。三九已至,卻和暖如春温自,著一層夾襖步出監(jiān)牢的瞬間玄货,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工悼泌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留松捉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓馆里,卻偏偏與公主長得像隘世,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸠踪,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355