深入理解JS

您所在的位置:网站首页 p20防抖在哪里设置 深入理解JS

深入理解JS

2023-12-30 18:14| 来源: 网络整理| 查看: 265

faa3aaea09d66ccf02c644b55ad078e6.jpeg

防抖定义

在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。(就像王者荣耀回城一样,只有在不被别人打断n秒后才能回去, 否则这期间有人打断就会重新计时。)

指触发事件后在规定时间内回调函数只能执行一次,如果在规定时间内又触发了该事件,则会重新开始算规定时间。

简单总结就是 延时执行 最后一次触发。

防抖.png 理解一下上图,用户多次频繁的触发某个事件,用户第一次触发事件后开始计时,在未满足时间间隔时就进行了第二次触发,那么将第一次触发的事件清除(这里含义是不让本次触发的事件执行) ,对第二次触发的事件重新开始计时;在未满足时间间隔时就进行了第三次触发,那么将第二次触发的事件清除,对第三次触发的事件重新开始计时,在满足时间间隔的一段时间内,用户未触发事件,则执行第三次触发的事件(即最后一次) 。同样的,上述过程完成后,用户又第一次触发了这个事件,开始计时,在未满足时间间隔时,用户第二次触发事件,那么将第一次触发的事件清除,对第二次触发的事件重新开始计时;在满足时间间隔的一段时间内,用户未触发事件,则执行第二次触发的事件(即最后一次) 。

代码分析

先了解定时器的用法:

需要借助setTimeout(JavaScript 函数, 等待的毫秒数) 函数(也称为定时器),来规定多少时间后执行 触发的事件 或 调用的函数。

clearTimeout() 方法可取消由 setTimeout() 方法设置的定时操作。clearTimeout() 方法的参数必须是由 setTimeout() 返回的 ID 值。

从上述过程,我们对代码要做的事进行分析:

用户行为程序行为触发事件(或调用函数)开始计时,使用定时器设置在满足时间间隔后,在定时器的回调函数中执行该事件(或调用该函数)。时间间隔内再次触发事件(或调用函数)将上一次触发事件的预定执行消除,重新计时时间间隔内没有再次触发事件(或调用函数)让刚刚最后一次 触发的的事件(或调用的函数) 按预期执行,也就是让setTimeout正常执行。 实现代码

通过定时器将回调函数进行延时.如果在规定时间内继续回调,发现存在之前的定时器,则将该定时器清除,并重新设置定时器.这里有个细节,就是后面所有的回调函数都要能访问到之前设置的定时器,这时就需要用到闭包(详见后面提到的)

 function debounce (fn, wait) {   let timer = null; //注意点1:借助闭包   return function (...args) {     if(timer) clearTimeout(timer); //注意点2:清除定时器     timer = setTimeout (() => {       fn.apply(this, args) //注意点3:setTimout会发生this隐式丢失;改变this指向为调用debounce所指的对象。     }, wait)   }  }  ​  //某个普通函数  function fn () {   //...  }  ​  //通过debounce函数包装后,返回一个fn的功能函数(此刻并没有调用这个功能函数)  let myFn = debounce(fn, 1000)

注意点:

1.为了使后面所有的回调函数都能访问到之前设置的定时器,使用闭包定义局部变量timer

个人理解:闭包形成的两个条件:1. 函数可以访问声明该函数的作用域上的变量 2. 函数的调用不在其声明作用域内。

《你不知道的JavaScript》:函数内部能访问到外部上级作用域的变量是因为作用域链的存在。从函数外部能访问函数内部的变量就是闭包。

MDN: 什么是闭包?闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。

代码第二行在外层定义了变量timer,并在内层函数使用了这个变量;同时debounce函数会返回一个fn功能版函数,当返回的这个fn功能版函数调用时,debounce函数已经执行完毕,但在功能函数中任然可以访问定义在debounce函数中的局部变量timer,也就是从debounce函数外部能访问debounce函数内部的变量,这就形成了闭包。以使后面所有的回调函数,都能访问到之前设置的定时器返回的ID。

2.最后一次调用函数,不会被清除。

在函数第一次被触发时,timer=null, 不会执行clearTimeout(timer);在规定的时间间隔内,进行第二次调用,将会在第一次定时器的回调函数还未执行时,就将其取消(即清除),对第二次触发进行重新计时,若在等待完规定的毫秒数后,没有新的触发,那么第二次的定时器回调函数正常执行。但在用户最后一次触发函数后,后续再也不会执行clearTimeout(timer),所以最后一次定义的定时器的回调函数会正常执行。

3.不要改变fn的原this指向(也即执行上下文的this属性), 改变this指向为调用debounce所指的对象

我们原本的动作是执行fn(), 假设此时fn内部的this指向thisFn。 将fn通过debounce函数包装以后,我们得到了一个新的功能函数myFn, 现在的动作变成了myFn()。并且在myFn内部去执行fn()。那么myFn内部的this指向thisFn,而fn内部的this指向window (默认绑定) ,这就改变了fn原本的this指向,是不合理的,我们不应该改变fn的功能,而是加强fn的功能。所以应该在myFn内部去执行fn()时,为fn绑定当前myFn的this指向。 并且在setTimeout的回调中调用fn时, 要考虑到setTimeout是宏任务,在当前同步代码结束后,才会调用setTimeout的回调函数,会造成this丢失,所以应该在setTimeout回调内部去执行fn()时,为fn绑定当前myFn的this指向。由于setTimeout回调是箭头函数,所以setTimeout回调内部的this就是返回的myFn的this指向。 进阶实现

防抖分为两种:

1)非立即执行版:事件触发->延时->执行回调函数;如果在延时中,继续触发事件,则会重新进行延时.在延时结束后执行回调函数.常见例子:就是input搜索框,客户输完过一会就会自动搜索 2)立即执行版:事件触发->执行回调函数->延时;如果在延时中,继续触发事件,则会重新进行延时.在延时结束后,并不会执行回调函数.常见例子:就是对于按钮防点击.例如点赞,心标,收藏等有立即反馈的按钮.

带有立即执行选项的防抖函数:

 //思路和上面的大致相同,如果是立即执行,则定时器中不再包含回调函数,而是在回调函数执行后,仅起到延时和重置定时器标识的作用  function debounce(fun, delay = 500,immediate = true) {     let timer = null //保存定时器     return function (args) {         let that = this         let _args = args   if (timer) clearTimeout(timer); //不管是否立即执行都需要首先清空定时器         if (immediate) {     if ( !timer) fun.apply(that, _args) //如果定时器不存在,则说明延时已过,可以立即执行函数   //不管上一个延时是否完成,都需要重置定时器             timer = setTimeout(function(){                 timer = null; //到时间后,定时器自动设为null,不仅方便判断定时器状态还能避免内存泄露             }, delay)         }         else {   //如果是非立即执行版,则重新设定定时器,并将回调函数放入其中             timer = setTimeout(function(){                 fun.call(that, _args)             }, delay);         }     }  }

立即执行版防抖函数:

立即执行版防抖.png

总结

函数防抖: 将几次操作合并为一此操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

应用场景

两个条件: 1,如果客户连续的操作会导致频繁的事件回调(可能引起页面卡顿). 2,客户只关心"最后一次"操作(也可以理解为停止连续操作后)所返回的结果. 例如:

search搜索联想,用户在不断输入值时,用防抖来节约请求资源。 不断的调整浏览器窗口大小会不断的触发resize事件,用防抖来让其只触发一次。 按钮点击:收藏,点赞,心标等 参考

链接:juejin.cn/post/684490…

链接:mp.weixin.qq.com/s/Vkshf-nED…



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3