根据我们之前所学,我们可以知道以下两点:
因此我们就可以通过防抖和节流来限制事件频繁的发生;
防抖和节流函数目前已经是前端实际开发中两个非常重要的函数,也是面试经常被问到的面试题。但是对他的理解不够深刻,不足以支撑我去面试,下面就是我复习之后对于防抖和节流的一些认识.
举一个比较常见的例子:
这是一个和生活的例子,我们再来举一个例子:
比如说我们在某购物软件里搜索 oppo
比如想要搜索一个oppo
:
o
时,为了更好的用户体验,通常会出现对应的联想内容,这些联想内容通常是保存在服务器的,所以需要一次网络请求;op
时,再次发送网络请求;oppo
一共需要发送4
次网络请求;但是我们需要这么多次的网络请求吗?
其实是不需要的,正确的做法应该是在合适的情况下再发送网络请求;
oppo
,那么只是发送一次网络请求;o
想了一会儿,这个时候 o
确实应该发送一次网络请求;这就是防抖的操作:
总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数
举一个很常见的例子:
上面的例子大家可能还有点没明白,那我们再来举一个更具代表意义的例子:
总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用
我们通过一个搜索框来延迟防抖函数的实现过程:
<body>
<script>
let search = document.getElementById('content');
let num = 0;
function searchChange() {
num++;
console.log(`发送${num}次网络请求`);
}
// 绑定oninput事件
search.oninput = searchChange
script>
body>
防抖的基本思想如下:
接下来,就是将思路转成代码即可:
function debounce(fn, delay) {
var timer = null;
return function () {
if (timer) clearTimeout(timer);
timer = setTimeout(function () {
fn();
}, delay);
}
}
// 对searchChange处理
var _searchChange = debounce(searchChange, 3000);
// 绑定oninput
search.oninput = _searchChange;
优化参数和this
我们知道在oninput
事件触发时会有参数传递,并且触发的函数中this
是指向当前的元素节点的
fn
的执行是一个独立函数调用,它里面的this
是window
function
中的this
指向的是节点对象;fn
在执行时是没有传递任何的参数的,它需要将触发事件时传递的参数传递给fn
function
中的arguments
正是我们需要的参数;function debounce(fn, delay) {
var timer = null;
return function() {
if (timer) clearTimeout(timer);
// 获取this和argument
var _this = this;
var _arguments = arguments;
timer = setTimeout(function() {
// 在执行时,通过apply来使用_this和_arguments
fn.apply(_this, _arguments);
}, delay);
}
}
优化取消功能
有时候,在等待执行的过程中,可能需要取消之前的操作:
我们这里将delay时间改长,并且在下方增加一个按钮:
<input class="search" type="text">
<button class="cancel-btn">取消事件</button>
<script>
function debounce(fn, delay) {
var timer = null;
var handleFn = function() {
if (timer) clearTimeout(timer);
// 获取this和argument
var _this = this;
var _arguments = arguments;
timer = setTimeout(function() {
// 在执行时,通过apply来使用_this和_arguments
fn.apply(_this, _arguments);
}, delay);
}
// 取消处理
handleFn.cancel = function() {
if (timer) clearTimeout(timer);
}
return handleFn;
}
</script>
<script>
// 1.获取输入框
var search = document.querySelector(".search");
// 2.监听输入内容,发送ajax请求
// 2.1.定义一个监听函数
var counter = 0;
function searchChange(e) {
counter++;
console.log("发送"+ counter +"网络请求");
console.log(this);
console.log(e.target.value);
}
// 对searchChange处理
var _searchChange = debounce(searchChange, 3000);
// 绑定oninput
search.oninput = _searchChange;
// 3.取消事件
var cancelBtn = document.querySelector(".cancel-btn");
cancelBtn.onclick = function(event) {
_searchChange.cancel();
}
</script>
优化立即执行
目前我们的事件触发都要等到delay时间,但是某些场景是用户开始输入时的第一次是立即执行的,后续的输入才需要等待,我们可以如何优化呢?
这个代码会一些复杂,在立即执行的地方需要进行更多的操作:
//优化立即执行
function debounce(fn, delay, leading) {
var timer = null;
leading = leading || false;
var handleFn = function () {
if (timer) clearTimeout(timer);
// 获取this和argument
var _this = this;
var _arguments = arguments;
if (leading) {
// 通过一个变量来记录是否立即执行
var isInvoke = false;
if (!timer) {
fn.apply(_this, _arguments);
isInvoke = true;
}
// 定时器通过修改timer来修改instant
timer = setTimeout(function () {
timer = null;
if (!isInvoke) {
fn.apply(_this, _arguments);
}
}, delay);
} else {
timer = setTimeout(function () {
// 在执行时,通过apply来使用_this和_arguments
fn.apply(_this, _arguments);
}, delay);
}
}
// 取消处理
handleFn.cancel = function () {
if (timer) clearTimeout(timer);
}
return handleFn;
}
因为防抖和节流函数都是对频繁事件的处理,所以我们可以使用相同的案例来演练,另外对应的优化操作也是比较相似的,所以这里不再进行细分,某些代码在实现过程中直接编写。
节流基本功能
节流函数的默认实现思路我们采用时间戳的方式来完成:
function throttle(fn,interval) {
//记录上一次执行的时间,设置为0
var last = 0;
return function() {
var _this = this;
var _arguments = arguments;
//获取当前时间
var nowTime = new Date().getTime();
//如果触发事件的事件间隔大于我们设置的,就执行函数
if(nowTime - last > interval) {
fn.apply(_this,_arguments);
//为last重新赋值为当前时间
last = nowTime;
}
}
}
优化最后执行
默认情况下,我们的防抖函数最后一次是不会执行的
now - last > interval
满足不了的我们来看一下代码如何实现:
else
语句:
timer
变量来记录定时器是否已经开启else
语句表示没有立即执行的情况下,就会开启定时器;timer
赋值为null
,这样的话可以开启下一次定时器;timer
需要赋值为null
null
,才能进行下一次的定时操作;function throttle(fn, interval) {
var last = 0;
var timer = null;
return function() {
// this和argument
var _this = this;
var _arguments = arguments;
var now = new Date().getTime();
if (now - last > interval) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(_this, _arguments);
last = now;
} else if (timer === null) { // 只是最后一次
timer = setTimeout(function() {
timer = null;
fn.apply(_this, _arguments);
}, interval);
}
}
}
我们可以传入一个变量让来确定是否需要最后执行一次:
function throttle(fn, interval, option) {
var last = 0;
var timer = null;
if (!option) option = {};
var trailing = option.trailing || false;
return function() {
// this和argument
var _this = this;
var _arguments = arguments;
var now = new Date().getTime();
if (now - last > interval) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(_this, _arguments);
last = now;
} else if (timer === null && trailing) { // 只是最后一次
timer = setTimeout(function() {
timer = null;
fn.apply(_this, _arguments);
}, interval);
}
}
}
优化取消功能
和防抖函数类似
function throttle(fn, interval) {
var last = 0;
var timer = null;
var handleFn = function() {
// this和argument
var _this = this;
var _arguments = arguments;
var now = new Date().getTime();
if (now - last > interval) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(_this, _arguments);
last = now;
} else if (timer === null) { // 只是最后一次
timer = setTimeout(function() {
timer = null;
fn.apply(_this, _arguments);
}, interval);
}
}
handleFn.cancel = function() {
clearTimeout(timer);
timer = null;
}
return handleFn;
}
优化返回值
function throttle(fn, interval, option) {
var last = 0;
var timer = null;
if (!option) option = {};
var trailing = option.trailing || false;
var result = option.result || null;
var handleFn = function() {
// this和argument
var _this = this;
var _arguments = arguments;
var now = new Date().getTime();
if (now - last > interval) {
if (timer) {
clearTimeout(timer);
timer = null;
}
callFn(_this, _arguments);
last = now;
} else if (timer === null && trailing) { // 只是最后一次
timer = setTimeout(function() {
timer = null;
callFn(_this, _arguments);
}, interval);
}
}
handleFn.cancel = function() {
clearTimeout(timer);
timer = null;
}
function callFn(context, argument) {
var res = fn.apply(context, argument);
if (result) {
result(res);
}
}
return handleFn;
}