首先,让我们看看什么是油门
1.定义
如果你拧紧水龙头,直到水以水滴的形式流出,你会发现每隔一段时间就会有一滴水流出。
也就是说,预先设置一个执行周期,当调用动作的时间大于或等于执行周期时,执行该动作,然后进入下一个新周期。
接口定义:
*连续调用频率控制返回函数时,动作的执行频率限定为/delay* @param delay {number}延迟时间,单位毫秒* @param action {function}请求关联函数,实际应用中需要调用的函数* @return {function}返回客户调用的函数*/delay (delay,action) 2。简单实现
Var throttle=function (delay,action){ var last=0 return function(){ var curr=new date()(if(curr-last delay){ action。apply (this,arguments) last=curr}}}我将在下面仔细解释这个节流函数。
在浏览器DOM事件中,有些事件会随着用户的操作不断被触发。例如,调整浏览器窗口大小、滚动浏览器页面和移动鼠标。也就是说,当用户触发这些浏览器操作时,如果在脚本中绑定了相应的事件处理方法,就会不断触发这个方法。
这不是我们想要的,因为有时候如果事件处理方法庞大,DOM操作复杂,而且这样的事件不断被触发,会造成性能损失,导致用户体验下降(UI响应慢、浏览器堵塞等)。).所以一般来说,我们会在相应的事件中加入延迟执行的逻辑。
一般来说,我们使用以下代码来实现这个功能:
var COUNT=0;函数TestFn(){ console . log(COUNT);}//当浏览器调整大小时//1。清除上一个定时器//2。添加一个定时器,将真实函数testFn延迟100毫秒,并触发窗口。onresize=function(){ vartimer=null;clearTimeout(计时器);timer=setTimeout(function(){ testFn();}, 100);};细心的同学会发现上面的代码其实是错误的,这是初学者会犯的一个问题:setTimeout函数的返回值应该保存在一个相对的全局变量中,否则每次调整大小都会生成一个新的计时器,这样就达不到我们发送的效果了
所以我们修改了代码:
var计时器=nullwindow . onresize=function(){ cleartime out(计时器);timer=setTimeout(function(){ testFn();}, 100);};此时代码正常,但是出现了新的问题——,产生了全局变量计时器。这是我们不想看到的。如果这个页面有其他功能,也叫定时器。不同的代码之前有冲突。为了解决这个问题,我们需要使用JavaScript的一个语言特性:闭包。相关知识的读者可以了解一下MDN,修改后的代码如下:
/* * *函数节流方法* @param function fn延迟调用函数* @param Number延迟多长时间延迟* @返回Function延迟执行方法*/var节气门=function (fn,delay){ vartimer=null;返回函数(){ clearTimeout(计时器);timer=setTimeout(function(){ fn();},延迟);}};window . on resize=throttle(TestFn,200,1000);我们用一个闭包函数(throttle throttling)把定时器放在内部,返回延迟处理函数,这样定时器变量对外部是不可见的,但是当内部延迟函数触发时,定时器变量也可以被访问。
当然,这种写法对于初学者来说很难理解。我们可以换一种写法来理解它:
var throttle=function (fn,延迟){ var timer=null返回函数(){ clearTimeout(计时器);timer=setTimeout(function(){ fn();},延迟);}};var f=油门(testFn,200);window . onresize=function(){ f();};这里要知道的主要一点是,调用throttle后返回的函数是真正的onresize被触发时需要调用的函数
现在看来这个方法已经接近完美,但实际上并非如此。例如:
如果用户不断调整浏览器窗口的大小,延迟处理功能将不会执行一次
因此,我们需要增加另一个功能:当用户触发resize时,应该在一定时间内至少触发一次。由于是在一定时间内,这个判断条件可以取当前时间毫秒。每次函数调用都会从上次调用时间中减去当前时间,然后判断如果差值大于一定时间就会直接触发,否则会经过超时的延时逻辑。
应该在下面的代码中指出:
previous变量的作用类似于timer,是记录上一次的标识。它必须是相对全局变量。如果逻辑流程遵循“至少触发一次”的逻辑,那么函数调用需要重置到当前时间之前。简单来说,和下一次相比,上一次其实是当前的
/* * *函数节流方法* @param function fn延迟调用函数* @param Number延迟多长时间* @param Number至少多久触发一次* @返回Function延迟执行方法*/var节气门=function (fn,延迟,至少){ vartimer=nullvar previous=null返回函数(){ var now=new Date();if(!先前)先前=现在;if(现在-以前至少){ fn();//将上次开始时间重置为当前结束时间previous=now} else { clearTimeout(计时器);timer=setTimeout(function(){ fn();},延迟);} }};练习:
我们模拟一个窗口滚动时节流的场景,也就是说当用户向下滚动页面时,我们需要节流并执行一些方法,比如计算DOM位置等需要连续操作DOM元素的动作
完整的代码如下:
!DOCTYPE html html lang=' en ' head meta charset=' UTF-8 ' title throttle/title/head body div style=' height :5000 px ' div id=' demo ' style=' position : fixed;/div /div脚本var COUNT=0,demo=document . getelementbyid(' demo ');函数test fn(){ demo . innerhtml=' test fn '已被调用' COUNT '次br ';} var throttle=function (fn,delay,至少){ var timer=nullvar previous=null返回函数(){ var now=new Date();if(!先前)先前=现在;if(至少现在-以前的至少){ fn();//将上次开始时间重置为当前结束时间previous=nowclearTimeout(计时器);} else { clearTimeout(计时器);timer=setTimeout(function(){ fn();previous=null},延迟);} } };window . on scroll=throttle(TestFn,200);//window . on scroll=throttle(TestFn,500,1000);/script/body/html我们使用两个案例来测试效果,分别是添加至少一个触发器至少参数和不添加:
//case 1 window . on scroll=throttle(TestFn,200);//case 2 window . on scroll=throttle(TestFn,200,500);案例一显示,页面滚动过程中不会调用testFN(无法停止),而是会调用一次,直到停止,也就是执行throttle中的最后一个setTimeout,效果如下:
情况2的表达式为:页面滚动过程中(无法停止),testFN会第一次延迟500ms(至少从延迟逻辑来看),然后至少每500ms执行一次,如下图
如上图,已经介绍了我们想要达到的效果,并给出了例子,希望对有需要的朋友有所帮助。后续的一些辅助优化读者可以自己琢磨,比如指向这个函数,保存返回值。总之,仔细了解这个过程感觉很好!