节流与防抖

节流(throttle)与防抖(debounce)

场景

因频繁执行DOM操作,资源加载等行为,导致UI停顿甚至浏览器崩溃。

  • window对象频繁的onresize,onscroll等事件
  • 拖拽的mousemove事件
  • 射击游戏的mousedown,keydown事件
  • 文字输入,自动完成的keyup事件

比如每次mouseover就会触发一次函数,又比如每次搜索一下就会向服务器发送一个请求,这样既没有意义,也很浪费资源。

解决方案

实际上对于window和resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多数的需求是以一定的频率执行后续处理。针对这两种需求出现了debounce(函数去抖)和throttle(函数节流)两种方式。

节流与防抖:

节流
比如mouseover,resize这种事件,每当有变化的时候,就会触发一次函数,这样很浪费资源。就比如一个持续流水的水龙头,水龙头开到最大的时候很浪费水资源,将水龙头开得小一点,让他每隔200毫秒流出一滴水,这样能源源不断的流出水而又不浪费。而节流就是每隔n的时间调用一次函数,而不是一触发事件就调用一次,这样就会减少资源浪费。

防抖
A和B说话,A一直bbbbbb,当A持续说了一段时间的话后停止讲话,过了10秒之后,我们判定A讲完了,B开始回答A的话;如果10秒内A又继续讲话,那么我们判定A没讲完,B不响应,等A再次停止后,我们再次计算停止的时间,如果超过10秒B响应,如果没有则B不响应。

节流与防抖的区别
节流与防抖的前提都是某个行为持续地触发,不同之处只要判断是要优化到减少它的执行次数还是只执行一次就行。

  • 节流例子,像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多。

  • 防抖例子,像仿百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求。

debounce(防抖)

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

非立即防抖:触发事件后函数不会立即执行,而是在n秒之后执行,如果n秒之内又触发了事件,则会重新计算函数执行时间。
立即防抖:触发事件后函数会立即执行,然后n秒内不触发事件才会执行函数的效果

非立即防抖

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)
    }
}

也可以将非立即执行版和立即执行版的防抖函数结合起来,实现最终的双剑合璧版的防抖函数。

/**
* @desc 函数防抖
* @param func (function) 函数
* @param wait (number) 延迟执行毫秒数
* @param immediate (boolean) true 表立即执行,false 表非立即执行
*/
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(节流)

节流分为时间戳定时版本

如果一个函数持续的,频繁的触发,那么就让他在一定的时间间隔后触发。

高频事件:
onscroll oninput resize onkeyup onkeydown onkerpress
onkeyup:每键入一个字母触发一次(并不是按照我们输入的汉字计算的)

节流单纯的降低代码执行的频率,保证一段时间内核心代码只执行一次。

时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候。

时间戳版

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;
        }
    }
}

定时器版本

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)
        }

    }
}

可以将时间戳版和定时器版的节流函数结合起来,实现双剑合璧版的节流函数。

/**
* @desc 函数节流
* @param func (function) 函数
* @param wait (number) 延迟执行毫秒数
* @param type  (number) 1 表时间戳版,2 表定时器版
*/
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; //显示地释放内存,防止内存泄漏
    };

    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;
};

总结

throttle和debounce均是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。比如说,我搜索时,onkeyup该几次还是几次,只是我的请求变少了,处理的逻辑少了,从而提高了性能。

1、节流(throttle): 创建一个节流函数,在等待时间内最多执行 一次的函数
2、防抖(debounce):创建一个 debounced(防抖动)函数,该函数会从上一次被调用后,延迟多少时间后调用方法,如果不停执行函数,执行时间被覆盖

案例

<template>
<div>
<button @click="throttleFun">点击按钮(节流)</button>


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


</div>
</template>
<script>
// 导入lodash 函数function段
import funHelper from 'lodash/function'

export default {
methods: {
// 防抖(延迟多少时间调用,如果一直keyup则会覆盖之前的时间重新计算)
debounceFun: funHelper.debounce((e)=>{
console.log(e.target.value);
}, 2000),
// 2秒内调用一次
// throttleFun: funHelper.throttle(()=>{
throttleFun: funHelper.throttle(function(){
// 如果使用()=> 箭头函数 this指向根实例,使用普通函数function()不改变this指向本组件
console.log(this);
console.log('2秒内只能调用一次!');
}, 2000, { 'trailing': false }),
//
throttleFun2(){
console.log('3秒内调用一次');
},
initFun(){
// 定义节流函数
let throttleF = funHelper.throttle(this.throttleFun2, 3000)
// 循环调用
for(let i=0;i<10;i++){
throttleF();
}
}
},
created(){
this.initFun();
}
}
</script>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容