♥️♥️♥️ 李哈哈要奋发啦~冲冲冲!!!相信只要坚持学习会有所回报的!♥️♥️♥️
1. 前言
- 本文参加了由公众号@若川视野发起的每周源码共度活动,点击了解详情一起参与。
- 这是源码共度的第25期,链接:juejin.cn/post/708744…
- github仓库:github.com/jashkenas/u…
前段时间刚好看完冴羽的专栏《JavaScript专题系列》的内容,里面就有《JavaScript专题之跟着underscore学防抖》,趁着源码共读活动,来做个总结,加深印象!
2.为什么要使用函数防抖
前端开发中,会遇到一些事件会在短时间内频繁触发,例如调整窗口大小(resize)或页面滚动(scroll),鼠标移动mousemove、mousedown等等。不做限制的话一秒就可能执行几十次,几百次。
如果在这些函数内部执行了其他函数,尤其是执行了操作 DOM 的函数(浏览器操作 DOM 是很耗费性能的),那不仅会浪费计算机资源,还会降低程序运行速度,甚至造成浏览器卡死、崩溃。除此之外,短时间内重复的 ajax 调用不仅会造成数据关系的混乱,还会造成网络拥塞,增加服务器压力。
3.函数防抖的原理
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
4.使用案例
<!-------html代码------->
<div id="container"></div>
//<------ js代码 ------>
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = debounce(getUserAction, 1000);
5.代码实现
• 简单版:首先会想到肯定要用到定时器setTimeout(),触发事件后在n秒后调用要执行的函数,如果在n秒内再次被触发则清除定时器,重新计时。注意使用debounce函数后,this的指向问题 && event对象*(参数)获取问题。
/*
** fn:要执行的函数
** wait:执行等待的时间
*/
function debounce(func, wait){
var timeout, args, context;
return function(){
context = this;
args = aguments;
clearTimeout(timeout);
timeout = setTimeout(function(){
//this指向windows,所以通过apply改变this的指向
func.apply(context, args)
},wait);
}
}
• 豪华版:上面代码已经很完善了,但想要更加的完美。希望函数可选择立即执行,而不是等到n秒后。加个 immediate 参数判断是否是立刻执行。且函数可能有返回值。
/*
** immediate:判断函数是否立即执行
*/
function debounce(func, wait, immediate){
var timeout, args, result, context;
return function(){
context = this;
args = aguments;
if(timeout) clearTimeout(timeout);
if(immediate){
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if(callNow){
result = func.apply(context, args);
}
}else{
timeout = setTimeout(function(){
//this指向windows,所以通过apply改变this的指向
func.apply(context,args);
},wait);
}
return result
}
}
⚠️注意:当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true 的时候返回函数的执行结果。
• 终极版:增加取消的功能,当时间间隔设置太长,立即执行后不想等,有个按钮可以取消debounce函数重新执行。
/*
** immediate:判断函数是否立即执行
*/
function debounce(func, wait, immediate){
var timeout, args, result, context;
var debounced = function(){
context = this;
args = aguments;
if(timeout) clearTimeout(timeout);
if(immediate){
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if(callNow){
result = func.apply(context, args);
}
}else{
timeout = setTimeout(function(){
//this指向windows,所以通过apply改变this的指向
func.apply(context,args);
},wait);
}
return result
}
debounced.cancel = function(){
clearTimeout(timeout);
timeout = null;
}
return debounced;
}
//使用
//let setAction = debounce(getUserAction, 1000, true);
//setAction.cancel();
以上是跟着冴羽前辈分享的文章学习后的复盘,思路清晰,由简单到复杂,易懂。让我们再学习下underscore的源码,看是否理解。
6.underscore源码
引入的now.js
export default Date.now || function(){
return new Date().getTime();
}
引入了restArgunments,github地址:🔗链接
import restArguments from './restArguments.js';
import now from './now.js';
export default function debounce(func, wait, immediate){
var timeout, previous, args, result, context;
var later = function(){
var passed = now() - previous; //前后点击的时间间隔
if(wait > passed){
timeout = setTimeout(later, wait - passed); //***问题一***
}else{
timeout = null;
if(!immediate) result = func.apply(context,args); //非立即执行时调用函数
//这个检查是必须的,因为func可以递归调用debounced
if(!timeout) args = context = null; //***问题二***
}
}
var debounced = restArguments(function(_args){
context = this;
args = _args;
previous = now(); //每次调用debounce函数都会获取到新的时间
if(!timeout){
timeout = setTimeout(later, wait);
if(immediate) result = func.apply(context, args); //立即执行时调用函数
}
return result;
})
debounced.cancel = function(){
clearTimeout(timeout);
timeout = args = context = null;
}
return debounced;
}
知识点:
- setTimeout是有返回值的,表示当前的setTimeout在页面中的所有setTimeout中的序号。
- function.length指的是函数的
形参个数,也就是函数定义时的参数个数,而不是函数实际接受的参数个数。arguments为函数接收到的所有实参组成的(类)数组。且箭头函数没有arguments。
function add(a,b){
console.log("fun.length:",add.length); //2
console.log("arguments",arguments); // Arguments(4)[1,2,3,4,callee:f,...]
return a+b
}
add(1,2,3,4)
- 剩余参数函数restArguments。
思考: (问题对应上面代码中标志系列)
问题一:当调用debounce函数wait>passed时,是不是都会创建一个定时器,这些定时器创建的越来越多,且没有及时调用clearTimeout清除,这样是不是不好。问题二:上面已经有 timeout = null,不就说明!timeout肯定为真吗,为什么这还要用if判断一下