js 防抖和节流

防抖

防抖是js优化的重要的一部分,也是面试中手写代码最常考的题目。那么我们为什么要防抖?防抖是什么意思?
比如我们在监听onkeyup事件中,监听input中输入的文字,我们可以在console中可以看到input的文字打印。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input id="input1" type="text">
</body>
<script src="index.js"></script>
</html>
const input1 = document.getElementById('input1');
// console.log(input1);
input1.addEventListener('keyup', function() {
    console.log(input1.value);
})

console打印

非常简单。但是每打一个字就监听到了,就很耗性能。
有人就问了,诶?那我能不能等我打完一小部分字段了以后,然后浏览器才去监听呢?
又有人说了,用setTimeout不就完了,过个一秒再监听。
所以,防抖这个词就来了。

所以我们在刚才的index.js中的代码中,input1的监听中,加入一个定时器试试:

const input1 = document.getElementById('input1');
// console.log(input1);
let timer = null;
input1.addEventListener('keyup', function() {
    if(timer) {
        clearTimeout(timer);
    }
    timer = setTimeout(() => {
        console.log(input1.value);
        // 清空定时器
        timer = null;
    }, 500)
})

先演示一下:


加入防抖功能

可以看到,当我直接依次输入123456的时候,停顿了一会,然后浏览器才监听到我输入了,并打印123456,接着我又输入了789停止,半秒后,又打印了123456789,很妙。就是说,你一直输一直输,只要你中间输入的间断不超过500毫秒(咱们自己定的时间),它就不会出来,就一直等着你输,什么时候你输入的间断,暂停的或者停止的时间超过500毫秒,它才会把最终的结果打印出来。

用很通俗的话说一下这里面的原理:
①先弄个定时器,监听到keyup的时候看一下现在定时器存在吗?不存在,是null,那我们不管,往下走。
②接下来给定时器赋值setTimeout的异步任务,这个异步任务是500毫秒之后执行,执行的结果就是打印当前的input中的value。打印完之后,要清空定时器。

流程是酱的:
①输入个1,停止:
timernullif语句不触发,往下进行,500毫秒后输出value,然后清空timer,这是第一次,而且只输入一个1的情况下,比较简单。
②输入123,停止:
timernullif语句不触发,往下进行,500毫秒之后执行,timer有值了,这个时候,我们立马就输入2了,keyup又监听了,这时候500毫秒还没有到,监听之后,timer是有值的,if条件判断为true,因为之前输入1的时候,timer就有值了,所以我们把之前输入1的那个timer给清除掉,然后下面重新设置timer,500毫秒之后执行,但是还没开始执行的时候,又输入3了,立马又会出现keyup事件,进入if,有timer,这个timer是输入2的时候定义的,然后又清空了,重新设置了定时任务,500毫秒只有执行,这时候已经没有值进来了,所以500毫秒到了以后,输出刚才的123。接下来如果再输入一个4,是和第一次输入一个1是一样的。

写的比较啰嗦,但是这就是传说中的防抖

但是有个问题,就是我们为了一个input1,就写了这么多行代码,感觉好麻烦啊,如果我们又10个input,我们是不是得写10个这样的监听函数?
所以新的问题就来了,我们需要改进一下我们的防抖函数,将它简单的封装一下:

我们需要将防抖函数封装成一个公共方法,那么这个方法的参数应该有个函数fn,和一个delay时间延迟,然后最终要返回一个函数。
代码如下:

// 防抖
function debounce(fn, delay = 500) {
    // timer 是在闭包中的
    let timer = null; 
    return function() {
        if(timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            // 清空定时器
            timer = null;
        }, delay)
    }
}
input1.addEventListener('keyup', debounce(function() {
    console.log(input1.value);
}), 600);

节流

但我们拖拽一个元素时,要随时拿到该元素被拖拽的位置,直接用drag事件,则会频发触发,很容易导致卡顿。节流的意思是:无论拖拽的速度有多快,都会每隔100ms触发一次。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #div1 {
            border: 1px solid #ccc;
            width: 200px;
            height: 100px;
        }
    </style>
</head>
<body>
    <div id="div1" draggable="true">可拖拽</div>
</body>
<script src="index.js"></script>
</html>
const div1 = document.getElementById('div1');
// console.log(div1);
div1.addEventListener('drag', function(e) {
    console.log(e.offsetX, e.offsetY);
})

拖拽打印

可以看到,drag基本上随时都在触发,这时候我们如果再调用别的js的功能,很容易造成负载验证,卡死。
所以我们和防抖一样,加个setTimeout

const div1 = document.getElementById('div1');
// console.log(div1);
let timer = null;
div1.addEventListener('drag', function(e) {
    if(timer) {
        return;
    }
    timer = setTimeout(() => {
        console.log(e.offsetX, e.offsetY);
        // 清空定时器
        timer = null;
    }, 100)
})

只不过这次如果不存在timer,我们直接return出去,就不管了,自生自灭,不响应了。知道100毫秒之后,打印了,并且timer被赋值为null,然后才会有timer的下一次setTimeout...具体的流程就不说了,和防抖是一样的。

同样,我们需要将节流函数封装一下,工具化:

// 节流
function throttle(fn, delay = 100) {
    let timer = null;
    return function() {
        if(timer) {
            return;
        }
        timer = setTimeout(() => {
            fn.apply(this, arguments);
            timer = null;
        }, delay)
    }
}

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

推荐阅读更多精彩内容

  • 使用window.addEventListener()进行窗口的resize、scroll、输入框实时监控等操作时...
    DDLH阅读 388评论 0 1
  • web开发中经常会做滚动监听,比如商品分类功能:左右两列,左侧类目,右侧商品,需要监听右测商品列表的滚动,滚到哪个...
    大海爱奔跑阅读 154评论 0 1
  • 一、什么是防抖和节流 Ps: 比如搜索框,用户在输入的时候使用change事件去调用搜索,如果用户每一次输入都去搜...
    拾柒_aab0阅读 272评论 0 1
  • 防抖: 触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行...
    _小飞飞阅读 387评论 0 1
  • 在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负...
    iqing2012阅读 788评论 0 1