js 防抖 节流 JavaScript
实际工作中,通过监听某些事件,如scroll事件检测滚动位置,根据滚动位置显示返回顶部按钮;如resize事件,对某些自适应页面调整DOM的渲染;如keyup事件,监听文字输入并调用接口进行模糊匹配等等,这些事件处理函数调用的频率如果太高,会加重浏览器的负担,减弱性能,造成用户体验不好。此时需要采用debounce(防抖)和throttle(节流)的方式来减少调用频率,同时不影响原来效果。
函数防抖(debounce)
当持续触发事件时,一段时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前就触发了事件,延时重新开始。
函数防抖的应用场景,最常见的就是用户注册时候的手机号码验证和邮箱验证了。只有等用户输入完毕后,前端才需要检查格式是否正确,如果不正确,再弹出提示语。
上图中,持续触发scroll事件时,并不执行handle函数,当1000毫秒内没有触发scroll事件时,才会延时触发scroll事件;
上面原理:对处理函数进行延时操作,若设定的延时到来之前,再次触发事件,则清除上一次的延时操作定时器,重新定时。
代码如下
// 函数防抖 var timer = false; document.getElementById("debounce").onscroll = function(){ clearTimeout(timer); // 清除未执行的代码,重置回初始化状态 timer = setTimeout(function(){ console.log("函数防抖"); }, 1000); };
防抖函数的封装使用
/** * 防抖函数 * @param method 事件触发的操作 * @param delay 多少毫秒内连续触发事件,不会执行 * @returns {Function} */ function debounce(method,delay) { let timer = null; return function () { let self = this, args = arguments; timer && clearTimeout(timer); timer = setTimeout(function () { method.apply(self,args); },delay); } } window.onscroll = debounce(function () { let scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log('滚动条位置:' + scrollTop); },1000)
另一种写法
// 防抖 function debounce(fn, wait) { var timeout = null; return function() { if(timeout !== null) clearTimeout(timeout); timeout = setTimeout(fn, wait); } } // 处理函数 function handle() { console.log("函数防抖"); } // 滚动事件 window.addEventListener('scroll', debounce(handle, 1000));
函数节流(throttlo)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
函数节流应用的实际场景,多数在监听页面元素滚动事件的时候会用到。
上图中,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数;
函数节流的要点是,声明一个变量当标志位,记录当前代码是否在执行。如果空闲,则可以正常触发方法执行。
代码如下:
// 函数节流 定时器 var canRun = true; document.getElementById("throttle").onscroll = function(){ if(!canRun){ // 判断是否已空闲,如果在执行中,则直接return return; } canRun = false; setTimeout(function(){ console.log("函数节流"); canRun = true; }, 300); };
节流函数的封装使用
//节流throttle代码(时间戳 var throttle = function(func, delay) { var prev = Date.now(); return function() { var context = this; var args = arguments; var now = Date.now(); if (now - prev >= delay) { func.apply(context, args); prev = Date.now(); } } } function handle() { console.log("函数节流"); } window.addEventListener('scroll', throttle(handle, 1000));
//节流throttle代码(定时器) var throttle = function(func, delay) { var timer = null; return function() { var context = this; var args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, delay); } } } function handle() { console.log("函数节流"); } window.addEventListener('scroll', throttle(handle, 1000));
// 节流throttle代码(时间戳+定时器): var throttle = function(func, delay) { var timer = null; var startTime = Date.now(); return function() { var curTime = Date.now(); var remaining = delay - (curTime - startTime); var context = this; var args = arguments; clearTimeout(timer); if (remaining <= 0) { func.apply(context, args); startTime = Date.now(); } else { timer = setTimeout(func, remaining); } } } function handle() { console.log("函数节流"); } window.addEventListener('scroll', throttle(handle, 1000));
用时间戳+定时器,当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数