節(jié)流與防抖

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

場(chǎng)景

因頻繁執(zhí)行DOM操作,資源加載等行為哗总,導(dǎo)致UI停頓甚至瀏覽器崩潰。

  • window對(duì)象頻繁的onresize,onscroll等事件
  • 拖拽的mousemove事件
  • 射擊游戲的mousedown灯变,keydown事件
  • 文字輸入敬肚,自動(dòng)完成的keyup事件

比如每次mouseover就會(huì)觸發(fā)一次函數(shù),又比如每次搜索一下就會(huì)向服務(wù)器發(fā)送一個(gè)請(qǐng)求倾贰,這樣既沒有意義冕碟,也很浪費(fèi)資源。

解決方案

實(shí)際上對(duì)于window和resize事件匆浙,實(shí)際需求大多為停止改變大小n毫秒后執(zhí)行后續(xù)處理安寺;而其他事件大多數(shù)的需求是以一定的頻率執(zhí)行后續(xù)處理。針對(duì)這兩種需求出現(xiàn)了debounce(函數(shù)去抖)和throttle(函數(shù)節(jié)流)兩種方式首尼。

節(jié)流與防抖:

節(jié)流
比如mouseover挑庶,resize這種事件言秸,每當(dāng)有變化的時(shí)候,就會(huì)觸發(fā)一次函數(shù)迎捺,這樣很浪費(fèi)資源举畸。就比如一個(gè)持續(xù)流水的水龍頭,水龍頭開到最大的時(shí)候很浪費(fèi)水資源凳枝,將水龍頭開得小一點(diǎn)抄沮,讓他每隔200毫秒流出一滴水,這樣能源源不斷的流出水而又不浪費(fèi)范舀。而節(jié)流就是每隔n的時(shí)間調(diào)用一次函數(shù)合是,而不是一觸發(fā)事件就調(diào)用一次,這樣就會(huì)減少資源浪費(fèi)锭环。

防抖
A和B說話聪全,A一直bbbbbb,當(dāng)A持續(xù)說了一段時(shí)間的話后停止講話辅辩,過了10秒之后难礼,我們判定A講完了,B開始回答A的話玫锋;如果10秒內(nèi)A又繼續(xù)講話蛾茉,那么我們判定A沒講完,B不響應(yīng)撩鹿,等A再次停止后谦炬,我們?cè)俅斡?jì)算停止的時(shí)間,如果超過10秒B響應(yīng)节沦,如果沒有則B不響應(yīng)键思。

節(jié)流與防抖的區(qū)別
節(jié)流與防抖的前提都是某個(gè)行為持續(xù)地觸發(fā),不同之處只要判斷是要優(yōu)化到減少它的執(zhí)行次數(shù)還是只執(zhí)行一次就行甫贯。

  • 節(jié)流例子吼鳞,像dom的拖拽,如果用消抖的話叫搁,就會(huì)出現(xiàn)卡頓的感覺赔桌,因?yàn)橹辉谕V沟臅r(shí)候執(zhí)行了一次,這個(gè)時(shí)候就應(yīng)該用節(jié)流渴逻,在一定時(shí)間內(nèi)多次執(zhí)行疾党,會(huì)流暢很多。

  • 防抖例子惨奕,像仿百度搜索雪位,就應(yīng)該用防抖,當(dāng)我連續(xù)不斷輸入時(shí)墓贿,不會(huì)發(fā)送請(qǐng)求茧泪;當(dāng)我一段時(shí)間內(nèi)不輸入了蜓氨,才會(huì)發(fā)送一次請(qǐng)求;如果小于這段時(shí)間繼續(xù)輸入的話队伟,時(shí)間會(huì)重新計(jì)算穴吹,也不會(huì)發(fā)送請(qǐng)求。

debounce(防抖)

防抖分為立即防抖非立即防抖
最常見的例子就是:搜索

非立即防抖:觸發(fā)事件后函數(shù)不會(huì)立即執(zhí)行嗜侮,而是在n秒之后執(zhí)行港令,如果n秒之內(nèi)又觸發(fā)了事件,則會(huì)重新計(jì)算函數(shù)執(zhí)行時(shí)間锈颗。
立即防抖:觸發(fā)事件后函數(shù)會(huì)立即執(zhí)行顷霹,然后n秒內(nèi)不觸發(fā)事件才會(huì)執(zhí)行函數(shù)的效果

非立即防抖

function debounce(func, wait) {
    var timeout = null;
    var context = this;
    var args = arguments;
    return function () {
        if (timeout) clearTimeout(timeout);
        timeout = setTimeout(function () {
            func.apply(context, args)
        }, wait);
    }
}

立即防抖

function debounce(func, wait) {
    var timeout = null;
    var context = this;
    var args = arguments;
    return function () {
        if (timeout) clearTimeout(timeout);
        var callNow = !timeout;
        timeout = setTimeout(function () {
            timeout = null;
        }, wait)
        if (callNow) func.apply(context, args)
    }
}

也可以將非立即執(zhí)行版和立即執(zhí)行版的防抖函數(shù)結(jié)合起來,實(shí)現(xiàn)最終的雙劍合璧版的防抖函數(shù)击吱。

/**
* @desc 函數(shù)防抖
* @param func (function) 函數(shù)
* @param wait (number) 延遲執(zhí)行毫秒數(shù)
* @param immediate (boolean) true 表立即執(zhí)行淋淀,false 表非立即執(zhí)行
*/
function debounce(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this;
        var args = arguments;
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(function () {
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function () {
                func.apply(context, args)
            }, wait);
        }
    }
}

大神代碼

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
_.debounce = function (func, wait, immediate) {
    var timeout, result;

    var later = function (context, args) {
        timeout = null;
        if (args) result = func.apply(context, args);
    };

    var debounced = restArgs(function (args) {
        if (timeout) clearTimeout(timeout);
        if (immediate) {
            var callNow = !timeout;
            timeout = setTimeout(later, wait);
            if (callNow) result = func.apply(this, args);
        } else {
            timeout = _.delay(later, wait, this, args);
        }

        return result;
    });

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

    return debounced;
};

throttle(節(jié)流)

節(jié)流分為時(shí)間戳定時(shí)版本

如果一個(gè)函數(shù)持續(xù)的,頻繁的觸發(fā)覆醇,那么就讓他在一定的時(shí)間間隔后觸發(fā)朵纷。

高頻事件:
onscroll oninput resize onkeyup onkeydown onkerpress
onkeyup:每鍵入一個(gè)字母觸發(fā)一次(并不是按照我們輸入的漢字計(jì)算的)

節(jié)流單純的降低代碼執(zhí)行的頻率,保證一段時(shí)間內(nèi)核心代碼只執(zhí)行一次永脓。

時(shí)間戳版和定時(shí)器版的節(jié)流函數(shù)的區(qū)別就是袍辞,時(shí)間戳版的函數(shù)觸發(fā)是在時(shí)間段內(nèi)開始的時(shí)候,而定時(shí)器版的函數(shù)觸發(fā)是在時(shí)間段內(nèi)結(jié)束的時(shí)候常摧。

時(shí)間戳版

function throttle(func, wait) {
    var previous = 0;
    return function () {
        var now = Date.now();
        var context = this;
        var args = arguments;
        if (now - previous > wait) {
            func.apply(context, args);
            previous = now;
        }
    }
}

定時(shí)器版本

function throttle(func, wait) {
    var timeout;

    return function() {
        var context = this;
        var args = arguments;
        if (!timeout) {
            timeout = setTimeout(function(){
                timeout = null;
                func.apply(context, args)
            }, wait)
        }

    }
}

可以將時(shí)間戳版和定時(shí)器版的節(jié)流函數(shù)結(jié)合起來搅吁,實(shí)現(xiàn)雙劍合璧版的節(jié)流函數(shù)。

/**
* @desc 函數(shù)節(jié)流
* @param func (function) 函數(shù)
* @param wait (number) 延遲執(zhí)行毫秒數(shù)
* @param type  (number) 1 表時(shí)間戳版落午,2 表定時(shí)器版
*/
function throttle(func, wait ,type) {
    if(type===1){
        var previous = 0;
    }else if(type===2){
        var timeout;
    }

    return function() {
        var context = this;
        var args = arguments;
        if(type===1){
            var now = Date.now();

            if (now - previous > wait) {
                func.apply(context, args);
                previous = now;
            }
        }else if(type===2){
            if (!timeout) {
                timeout = setTimeout(function(){
                    timeout = null;
                    func.apply(context, args)
                }, wait)
            }
        }

    }
}

  • 大神代碼
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
_.throttle = function (func, wait, options) {
    var timeout, context, args, result;
    var previous = 0;
    if (!options) options = {};

    var later = function () {
        previous = options.leading === false ? 0 : _.now();
        timeout = null;
        result = func.apply(context, args);
        if (!timeout) context = args = null; //顯示地釋放內(nèi)存谎懦,防止內(nèi)存泄漏
    };

    var throttled = function () {
        var now = _.now();
        if (!previous && options.leading === false) previous = now;
        var remaining = wait - (now - previous);
        context = this;
        args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timeout) {
                clearTimeout(timeout);
                timeout = null;
            }
            previous = now;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        } else if (!timeout && options.trailing !== false) {
            timeout = setTimeout(later, remaining);
        }
        return result;
    };

    throttled.cancel = function () {
        clearTimeout(timeout);
        previous = 0;
        timeout = context = args = null;
    };

    return throttled;
};

總結(jié)

throttle和debounce均是通過減少實(shí)際邏輯處理過程的執(zhí)行來提高事件處理函數(shù)運(yùn)行性能的手段,并沒有實(shí)質(zhì)上減少事件的觸發(fā)次數(shù)板甘。比如說党瓮,我搜索時(shí)详炬,onkeyup該幾次還是幾次盐类,只是我的請(qǐng)求變少了,處理的邏輯少了呛谜,從而提高了性能在跳。

1、節(jié)流(throttle): 創(chuàng)建一個(gè)節(jié)流函數(shù)隐岛,在等待時(shí)間內(nèi)最多執(zhí)行 一次的函數(shù)
2猫妙、防抖(debounce):創(chuàng)建一個(gè) debounced(防抖動(dòng))函數(shù),該函數(shù)會(huì)從上一次被調(diào)用后聚凹,延遲多少時(shí)間后調(diào)用方法割坠,如果不停執(zhí)行函數(shù)齐帚,執(zhí)行時(shí)間被覆蓋

案例

<template>
<div>
<button @click="throttleFun">點(diǎn)擊按鈕(節(jié)流)</button>


<input type="text" @keyup="debounceFun" />


</div>
</template>
<script>
// 導(dǎo)入lodash 函數(shù)function段
import funHelper from 'lodash/function'

export default {
methods: {
// 防抖(延遲多少時(shí)間調(diào)用,如果一直keyup則會(huì)覆蓋之前的時(shí)間重新計(jì)算)
debounceFun: funHelper.debounce((e)=>{
console.log(e.target.value);
}, 2000),
// 2秒內(nèi)調(diào)用一次
// throttleFun: funHelper.throttle(()=>{
throttleFun: funHelper.throttle(function(){
// 如果使用()=> 箭頭函數(shù) this指向根實(shí)例,使用普通函數(shù)function()不改變this指向本組件
console.log(this);
console.log('2秒內(nèi)只能調(diào)用一次!');
}, 2000, { 'trailing': false }),
//
throttleFun2(){
console.log('3秒內(nèi)調(diào)用一次');
},
initFun(){
// 定義節(jié)流函數(shù)
let throttleF = funHelper.throttle(this.throttleFun2, 3000)
// 循環(huán)調(diào)用
for(let i=0;i<10;i++){
throttleF();
}
}
},
created(){
this.initFun();
}
}
</script>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末彼哼,一起剝皮案震驚了整個(gè)濱河市对妄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌敢朱,老刑警劉巖剪菱,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拴签,居然都是意外死亡孝常,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蚓哩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來构灸,“玉大人,你說我怎么就攤上這事岸梨《逞海” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵盛嘿,是天一觀的道長洛巢。 經(jīng)常有香客問我,道長次兆,這世上最難降的妖魔是什么稿茉? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮芥炭,結(jié)果婚禮上漓库,老公的妹妹穿的比我還像新娘。我一直安慰自己园蝠,他們只是感情好渺蒿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著彪薛,像睡著了一般茂装。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上善延,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天少态,我揣著相機(jī)與錄音,去河邊找鬼易遣。 笑死彼妻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播侨歉,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼屋摇,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了幽邓?” 一聲冷哼從身側(cè)響起摊册,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颊艳,沒想到半個(gè)月后茅特,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棋枕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年白修,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片重斑。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兵睛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窥浪,到底是詐尸還是另有隱情祖很,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布漾脂,位于F島的核電站假颇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏骨稿。R本人自食惡果不足惜笨鸡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坦冠。 院中可真熱鬧形耗,春花似錦、人聲如沸辙浑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽判呕。三九已至倦踢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間佛玄,已是汗流浹背硼一。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工累澡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梦抢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓愧哟,卻偏偏與公主長得像奥吩,于是被迫代替她去往敵國和親哼蛆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容