顾名思义,防抖就是防止抖动,我们在发抖的时候是不是不停地动呢,防抖就是只动一次就好了,不要一直抖。
防抖就是在一段时间内,将连续的多次触发事件,转化为只触发一次,可以是只触发第一次,也可以是只触发最后一次
在实际应用中,对于那些触发很快的事件,如在input中输入搜索的内容,从服务端返回数据,我们肯定不希望在我们还没有输入完成的时候请求就发出去,这是很低性能的行为,因此我们需要手动进行防抖
我们来实现一下吧
先建立一个简单的使用场景,省略一些html标签,相信大家很容易看懂
<style> #container{ width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px; } </style> <body> <div id="container"></div> </body>
// 定义count,我们的事件就是要让count不停累加 var count = 1; var container = document.getElementById('container'); // 累加方法 function getUserAction() { container.innerHTML = count++; };
第一版:
实现原理:利用闭包与定时器,闭包的作用就是将timeout给记住,(不太了解闭包可以看闭包 - JavaScript | MDN)每次触发鼠标移动事件,先清除定时器,再重新设置一个新的定时器,定时器时间到了才会触发事件
const debounce1 = (func, delay) => { let timeout; return function() { clearTimeout(timeout); timeout = setTimeout(func, delay); } } // 测试 // 可以看到,鼠标移动一下,1s后数字加1,鼠标移动后1s内继续移动,数字不会加1,只有当鼠标停止移动1s后,数字才会加1 container.onmousemove = (debounce1(getUserAction, 1000));
当然,简易版存在着一些问题
问题一:this的指向发生了改变
我们对绑定的元素触发事件的回调函数时,事件函数的this应该是指向被绑定元素的,也就是<div id="container"></div>
,但是由于我们将事件的回调函数作为参数传给了debounce函数,因此这里的this指向发生了隐式丢失,指向了window
问题二:事件对象发生了改变
我们在调用事件时,会给事件回调函数传入事件对象event,但是由于我们对事件回调函数使用debounce进行了封装,返回出来的函数中的事件对象已经丢失了
改进的方法:将this原来的指向和event对象传入闭包中
const debounce2 = (func, delay) => { let timeout; return function() { // 保存this指向 let context = this; // 保存调用事件回调函数时传入的参数(event对象) let args = arguments; clearTimeout(timeout); timeout = setTimeout(function() { func.apply(context, args) }, delay) } } // 测试 function getUserAction(e) { // 测试event对象 console.log(e); // 测试this指向 console.log(this); container.innerHTML = count++; }; container.onmousemove = (debounce2(getUserAction, 1000));
前面有说到,防抖就是在一段时间内,将连续的多次触发事件,转化为只触发一次,可以是只触发第一次,也可以是只触发最后一次
我们前面实现的是触发的最后一次才调用事件的回调函数,那么我们可不可以控制,让事件在第一次触发时就被调用,在规定时间内不再被调用呢?当然可以!
使用immediate参数作为控制事件回调函数何时调用的判断条件,true则立即调用,false则在最后一次触发才调用
const debounce3 = (func, delay, immediate) => { let timeout, result; return function() { // 保存this指向 let context = this; // 保存调用事件回调函数时传入的参数(event对象) let args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { let callNow = !timeout; timeout = setTimeout(function() { timeout = null; }, delay) if (callNow) { return result = func.apply(context, args); } } else { timeout = setTimeout(function() { func.apply(context, args) }, delay); } } return result; } // 测试 container.onmousemove = debounce3(getUserAction, 2000, true)
本文探讨了防抖的实现与应用,对于防抖的实现,要注意闭包调用时参数传递的丢失和this指向的丢失,同时还可以实现一个可以控制在什么时候触发的防抖函数。
本篇文章就到这里啦,下次我们聊一聊和防抖很像的节流