导读:在进行窗口的resize、scroll,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时我们可以采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时又不影响实际效果。
函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。例如用scroll事件举例子:持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件。
一起来实现个简单的debounce~
function debounce(fn, wait) { var timeout = null; return function() { if(timeout !== null) clearTimeout(timeout); timeout = setTimeout(fn, wait); } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', debounce(handle, 1000));
上诉代码解析
//这里用到一个闭包函数的形式封装 //第一个参数是我们需要事件处理的函数 //第二个参数是执行事件函数需要的时间 function debounce(fn, wait) { //创建一个定时器变量 var timeout = null; // 返回一个可执行函数,也可称为闭包函数 return function() { // 如果定时器变量不为空则清除这个定时器对象 if(timeout !== null) clearTimeout(timeout); //创建一个定时器对象并赋值给timeout变量 timeout = setTimeout(fn, wait); } } // 我们需要事件处理的函数 function handle() { console.log(Math.random()); } // 滚动事件 //页面加载过程中会注册浏览器scroll监听事件 //并执行我们的封装函数debounce,返回一个可执行闭包函数作为scroll事件的处理函数 window.addEventListener('scroll', debounce(handle, 1000));
函数节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。例如用scroll事件举例子,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
函数节流主要有两种实现方法:时间戳和定时器。接下来分别用两种方法实现throttle~
节流throttle代码(时间戳):
var throttle = function(func, delay) { var prev = Date.now(); return function() {var now = Date.now(); if (now - prev >= delay) { func(); prev = Date.now(); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000));
上述代码解析
//这里用到一个闭包函数的形式封装 //第一个参数是事件处理函数 //第二个参数是执行事件函数需要的时间 var throttle = function(func, delay) { // 获取到事件初始化时间戳也就是时间的毫秒数,并作为事件的上一次触发的时间 var prev = Date.now(); //返回一个函数 return function() { // 获取到触发事件处理函数的时间戳也就是当前时间的毫秒数 var now = Date.now(); // 如果当前的时间减去上一次触发事件的时间大于等于我们给的执行事件函数时间 if (now - prev >= delay) { //执行事件处理函数 func(); // 保存此时执行事件处理函数的时间作为上一次触发的时间 prev = Date.now(); } } } // 我们需要事件处理的函数 function handle() { console.log(Math.random()); } // 滚动事件 //页面加载过程中会注册浏览器scroll监听事件 //并执行我们的封装函数throttle,返回一个可执行闭包函数作为scroll事件的处理函数 window.addEventListener('scroll', throttle(handle, 1000));
节流throttle代码(定时器):
var throttle = function(func, delay) { var timer = null; return function() { if (!timer) { timer = setTimeout(function() { func(); timer = null; }, delay); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000));
上述代码解析
//这里用到一个闭包函数的形式封装 //第一个参数是事件处理函数 //第二个参数是执行真实事件函数需要的时间 var throttle = function(func, delay) { // 创建一个变量用来保存定时器对象 var timer = null; //返回一个函数 return function() { //如果这个对象为空,则创建一个定时器对象保存到timer变量中 //如果这个不为空则说明上一次执行的真实事件处理函数在等待时间到达delay执行 if (!timer) { timer = setTimeout(function() { //执行真实事件函数 func(); // 清空timer对象 timer = null; }, delay); } } } // 我们真实需要事件执行的函数 function handle() { console.log(Math.random()); } // 滚动事件 //页面加载过程中会注册浏览器scroll监听事件 //并执行我们的封装函数throttle,返回一个可执行闭包函数作为scroll事件的处理函数 window.addEventListener('scroll', throttle(handle, 1000));
上述代码执行过程:
当触发事件的时候,我们设置一个定时器,再次触发事件的时候,如果定时器存在,就不执行,直到delay时间后,定时器执行执行函数,并且清空定时器,这样就可以设置下个定时器。当第一次触发事件时,不会立即执行函数,而是在delay秒后才执行。而后再怎么频繁触发事件,也都是每delay时间才执行一次。当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数。
节流中用时间戳或定时器都是可以的。更精确地,可以用时间戳+定时器,当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数。
节流throttle代码(时间戳+定时器):
var throttle = function(func, delay) { var timer = null; var startTime = Date.now(); return function() { var curTime = Date.now(); var remaining = delay - (curTime - startTime); timer && clearTimeout(timer); if (remaining <= 0) { func(); startTime = Date.now(); } else { timer = setTimeout(func, remaining); } } } function handle() { console.log(Math.random()); } window.addEventListener('scroll', throttle(handle, 1000));
上述代码解析
//这里用到一个闭包函数的形式封装 //第一个参数是事件处理函数 //第二个参数是执行真实事件函数需要的时间 var throttle = function(func, delay) { // 创建一个变量用来保存定时器对象 var timer = null; // 创建一个变量用来保存初始化事件时间戳 var startTime = Date.now(); //返回一个函数 return function() { // 创建一个变量用来保存事件执行当前函数时间戳 var curTime = Date.now(); // 用我们给定的时间减去(当前的时间戳和上一次的时间戳的差) //得到的值是与给定的时间还差多少毫秒并保存到变量中 var remaining = delay - (curTime - startTime); //如果timer不为空则清除定时器timer timer && clearTimeout(timer); //如果这个变量小于等于零,说明这个时间点是在时间delay以后 if (remaining <= 0) { //执行真实事件函数 func(); //将此时的时间戳保存作为上一次执行真实事件函数的时间戳 startTime = Date.now(); } else { // 创建一个时间对象保存给timer,并将remaining值作为执行真实事件函数的时间 timer = setTimeout(func, remaining); } } } // 我们真实需要事件执行的函数 function handle() { console.log(Math.random()); } // 滚动事件 //页面加载过程中会注册浏览器scroll监听事件 //并执行我们的封装函数throttle,返回一个可执行闭包函数作为scroll事件的处理函数 window.addEventListener('scroll', throttle(handle, 1000));
上述代码执行过程:
在节流函数内部使用开始时间startTime、当前时间curTime与delay来计算剩余时间remaining,当remaining<=0时表示该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数和每隔delay时间执行一次事件处理函数,此处说明:因为在网页加载的时候throttle函数就已经执行了,所以当中会保存初始化的startTime,而当我们触发事件的时候中间会有一段时间的间隔,如果这个时间间隔大于delay的话,那么就会执行真实需要事件处理的函数)。如果还没到时间的话就设定在remaining时间后再触发 (保证了最后一次触发事件后还能再执行一次事件处理函数)。当然在remaining这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。
关于js的防抖与节流就介绍到这里了~
本篇文章的编写参考于:mp.weixin.qq.com