函数防抖与节流
- 函数防抖: 函数防抖就是对于一定时间段的连续的函数调用,只让其执行一次。
-
函数节流: 降低触发回调的频率,让一个函数不要执行得太频繁,减少一些过快的调用来节流。
// 函数防抖:延时执行:需要间隔delay时间触发才会生效 // 函数防抖的应用场景:输入框搜索自动补全事件,频繁操作点赞和取消点赞,滚动事件,resize窗口变化 // 简单的防抖动函数 function debounce(func, wait) { // 定时器变量 var timeout; return function() { // 每次触发 scroll handler 时先清除定时器 clearTimeout(timeout); // 指定 xx ms 后触发真正想进行的操作 handler timeout = setTimeout(func, wait); }; }; // 实际想绑定在 scroll 事件上的 handler function realFunc(){ console.log("Success"); } // 采用了防抖动 window.addEventListener('scroll',debounce(realFunc,500)); // 没采用防抖动 window.addEventListener('scroll',realFunc); // 封装版 /** * @desc 函数防抖 * @param func 函数 * @param wait 延迟执行毫秒数 * @param immediate true 表立即执行,false 表非立即执行 */ function debounce(func,wait,immediate) { let timeout; return function () { // 获取 this 和 参数,是为了让 debounce 函数最终返回的函数 this 指向不变以及依旧能接受到 e 参数。 let context = this; // 这里this指向调用debounce函数的对象 let args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait) if (callNow) func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } } }
// 函数节流 throttle // 原理:当达到了一定的时间间隔就会执行一次;可以理解为是缩减执行频率,即每隔多少事件执行一次 // 应用场景:如果想要每隔一段时间发送一次请求,而不是等到客户触发最后一次操作才发送请求,可以这样实现 // 鼠标不断点击触发,mousedown(单位时间内只触发一次),监听滚动事件,比如是否滑到底部自动加载更多,搜索框,向后台发送请求 /** * @desc 函数节流 * @param func 函数 * @param wait 延迟执行毫秒数 * @param type 1 表时间戳版,2 表定时器版 */ function throttle(func, wait ,type) { if(type===1){ let previous = 0; }else if(type===2){ let timeout; } return function() { let context = this; let args = arguments; if(type===1){ let now = Date.now(); if (now - previous > wait) { func.apply(context, args); previous = now; } }else if(type===2){ if (!timeout) { timeout = setTimeout(() => { timeout = null; func.apply(context, args) }, wait) } } } }
tips: 普通函数 return this, this的指向取决于调用函数的对象。在setInterval和setTimeout中传入函数时,函数中的this会指向window对象。
function LateBloomer() { this.petalCount = Math.ceil(Math.random() * 12) + 1; } // Declare bloom after a delay of 2 second LateBloomer.prototype.bloom = function() { // 这个写法会报 I am a beautiful flower with undefined petals! // 原因:在setInterval和setTimeout中传入函数时,函数中的this会指向window对象 window.setTimeout(this.declare, 2000); // 如果写成 window.setTimeout(this.declare(), 2000); 会立即执行,就没有延迟效果了。 }; LateBloomer.prototype.declare = function() { console.log('I am a beautiful flower with ' + this.petalCount + ' petals!'); }; var flower = new LateBloomer(); flower.bloom(); // 二秒钟后, 调用'declare'方法
解决办法:
- 将bind换成call,apply也会导致立即执行,延迟效果会失效window.setTimeout(this.declare.bind(this), 2000);
- 使用es6中的箭头函数,因为在箭头函数中this是固定的。箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。window.setTimeout(() => this.declare(), 2000);
- setTimeout this指向
// 函数作为方法调用还是构造函数调用,this是不同的 function Foo() { this.value = 42; this.method = function() { // this 指向全局对象 alert(this) // 输出window alert(this.value); // 输出:undefined }; setTimeout(this.method, 500); // this指向Foo的实例对象 } new Foo();
-
setTimeout中的延迟执行代码中的this永远都指向window
-
setTimeout(this.method, time)这种形式中的this,即上文中提到的第一个this,是根据上下文来判断的,默认为全局作用域,但不一定总是处于全局下,具体问题具体分析。
-
setTimeout(匿名函数, time)这种形式下,匿名函数中的变量也需要根据上下文来判断,具体问题具体分析。在这里匿名函数的使用形成了一个闭包,从而能访问到外层函数的局部变量。
-
可以理解setTimeout函数类似挂起的函数或者事件,当延时时间到,主进程才会将setTimeout函数在widows作用域下执行(JS执行过程可以简单理解为一个进程+一个任务队列,队列中是等待被执行的延时函数或者事件回调的函数等等)。