共计 3863 个字符,预计需要花费 10 分钟才能阅读完成。
前言
前端开发中会遇到一些频繁的事件触发,比如:window 的 scroll、resize;mousedown、mousemove,keyup、keydown 等等,假如你对自己的代码不做什么的处理,你会发现页面卡顿、触发接口请求频繁等问题,本文将浅析函数节流跟防抖实现,一步一步逐渐揭开函数节流跟防抖的真面目????
概念
理解防抖跟节流触发原理,根据不同使用场景合理使用
函数防抖(debounce)
当调用动作过 n 毫秒后,才会执行该动作,若在这 n 毫秒内又调用此动作则将重新计算执行时间, 不会执行
理解原理: 尽管触发事件,但是一定在事件触发 n 秒后才执行,如果在一个事件触发的 n 秒内又触发了这个事件,就以新的事件的时间为准,n 秒后才执行,总之,就是要等触发完事件 n 秒内不再触发事件,才会执行!
函数节流(throttle)
预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期
理解原理: 规定时间内,保证执行一次该函数
实现
防抖
根据防抖原理,实现代码如下, 之后的示例都将是常规使用:
# 箭头 log 函数
let count = 0
const log = () => {
console.log(this)
++count
console.log(count)
}
# 函数表达式
const log = function (evt) {
console.log(this)
console.log(evt)
++count
console.log(count)
}
const debounce = function (fn, delay){
let timeout = null
return function () {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(fn, delay)
}
}
使用频繁 click 事件为例常规使用: meContain.onclick = debounce(log, 1000)react demo: …onClick={debounce(log.bind(this), 1000)} 通过例子你会发现,1s 之内频繁 click,都会在最后输出一次 log,验证了防抖原理
小伙伴们有没有发现此时的防抖函数仍存在缺陷
this 指向和 event 对象
假如用户现在一直点击提交按钮的话,就会一直不发出请求,也得不到任何提示,对用户体验相当不好
this 指向和 event 对象
this 指向
log 函数中 console.log(this), 不使用 debounce 函数的时候,this 的值为:undefined, 这是因为使用了箭头函数,此时需要 onClick 调用的时候 bind(this), this 指向 react 组件示例
常规使用中 console.log(this),不使用 debounce 函数的时候,this 的值为:
<div id=”mecontain”></div>
event 对象
不使用 debouce 函数,会打印 ClickEvent 对象
debounce 函数中,却只会打印 undefined
解决以上问题,来更改我们的代码
const debounce = function (fn, delay){
let timeout = null
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout)
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
交互优化体验
如果希望立刻执行函数一次,不用等到事件停止触发后才执行,然后等到停止触发 n 秒后,再可以重新触发执行 <br/> 通过添加 isImmeDiate 来判断是否立刻执行
const debounce = function (fn, delay,isImmeDiate= false){
let timeout = null
return function () {
const context = this;
const args = arguments;
if(timeout) clearTimeout(timeout)
if(isImmeDiate) {
# 判断是否已经执行过,不要重复执行
let callNow = !timeout
timeout = setTimeout(function(){
timeout = null;
}, delay)
if(callNow) result = fn.apply(context, args)
} else {
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
return result
}
}
如果要添加一个取消 debounce 开关,只需要添加一个 cancle 函数清除定时器 timeout = null
const debounce = function (fn, delay,isImmeDiate= false){
let timeout = null
const debounced = function () {
const context = this;
const args = arguments;
if(timeout) clearTimeout(timeout)
if(isImmeDiate) {
# 判断是否已经执行过,不要重复执行
# setTimeout 也是一直在更新的
let callNow = !timeout
timeout = setTimeout(function(){
timeout = null;
}, delay)
if(callNow) result = fn.apply(context, args)
} else {
timeout = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
return result
}
debounced.prototype.cancle = function() {
clearTimeout(timeout)
timeout = null
}
return debounced
}
到这里我们实现了一个防抖函数,但是小伙伴们有没有别的想法呢?
节流
根据节流原理,实现代码如下, 之后的示例都将是常规使用:
时间戳实现
const throttle = function (fn, delay) {
let preTime = 0;
return function () {
const context = this;
const args = arguments;
const now = +new Date();
if (now – preTime > delay) {
fn.apply(context, args);
preTime = now;
}
}
}
定时器实现
const throttle = function (fn, delay) {
let timeout = null
return function () {
const context = this;
const args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null
fn.apply(context, args)
}, delay)
}
}
}
# 如果需要立刻执行,其实变更下执行顺序即可
timeout = setTimeout(function(){
timeout = null
//fn.apply(context, args)
}, delay)
fn.apply(context, args)
同样使用频繁 click 事件为例常规使用: meContain.onclick = throttle(log, 1000) 通过例子你会发现,频繁 click,都会在根据你设定的周期输出一次 log,验证了节流原理
小伙伴们有没有发现此时的节流函数存在的特点
时间戳会立刻执行,定时器会在 n 秒后第一次执行
时间戳停止触发后没有办法再执行事件,定时器实现停止触发后依然会再执行一次事件
合并两者特点
const throttle = function (fn, delay) {
let timeout = null
let preTime = 0;
const later = function() {
preTime = +new Date()
timeout = null
fn.apply(context, args);
}
const throttled = function () {
const context = this;
const args = arguments;
const now = +new Date();
#下次触发 fn 剩余的时间
const remaining = delay – (now – preTime)
#如果没有剩余的时间了或者系统时间变更
if (remaining <= 0 || remaining > delay) {
if(timeout) {
clearTimeout(timeout)
timeout = null
}
preTime = now
fn.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining)
}
}
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
return throttled
}
总结
节流,在规定时间内,保证执行一次该函数;防抖,当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时
文档
浅谈函数防抖函数节流函数防抖函数节流
自我推荐
深入源码了解 Vue 错误处理 Webpack DllPlugin 让构建速度柔顺丝滑