前言
相信你经常在项目里面要接入一个防抖或者节流的需求。但是看着要把这个debounce方法接入到项目要改好多啊。对此我将debounce进行优化可以只修改一行代码直接无缝接入项目。
分为两部分介绍。
- 结合debounce和throttle,基础版
- 优化版debounce方法 首先来了解一下什么是防抖节流把。这边网上介绍的有很多。我找到一篇比较优质的。读者请先了解。
第一步,结合debounce和throttle,基础版:
根据以上文章我们可以提炼出。debounce和throttle结合的基础代码:
/**
* 防抖 节流
* @param func 方法
* @param wait 延迟时间
* @param throttle 时间到多少了一定先执行, false 是防抖,true 节流
* @returns {function(): void}
*/
const debounce = (func, wait = 500, throttle = false) => {
let delay = wait || 500
let timer
return function () {
const _this = this
let args = arguments
if (timer) {
if (throttle) return
else clearTimeout(timer)
}
timer = setTimeout(() => {
timer = null
func.apply(_this, args)
}, delay)
}
}
console.log('now time', new Date().toLocaleTimeString())
function testFunction (inString) {
console.log(inString)
console.log('now', new Date().toLocaleTimeString())
}
let debounceFunction = debounce(testFunction, 2000)
debounceFunction('debounceFunction start')
setTimeout(() => debounceFunction('debounceFunction start'), 1000)
let throttleFunction = debounce(testFunction, 2000, true)
throttleFunction('throttleFunction start')
setTimeout(() => throttleFunction('throttleFunction start'), 1000)
输出
now time 17:46:18
throttleFunction start
now 17:46:20
debounceFunction start
now 17:46:21
但是这个结合到项目里面真的就很方便吗?
如果我想让testFunction方法加上debounce的功能,我得先根据testFunction结合debounce生成一个debounceFunction,在调用这个方法才可以。现在可能觉得没什么。但是要把这个方法加到项目里面就很有点问题了。看例子:
情景一,按钮加防抖:
<template>
·······
<el-button @click="searchEvent">查询</el-button>
·······
</template>
<script>
export default {
methods: {
searchEvent () {
// 查看参数是否对
this.initData()
},
initData () {
// ······todo something
}
}
}
这个需求应该很普遍吧。在按钮上做防抖。如果是你会怎么做。可以很快想到,在initData方法外面套一层debounce就能解决。
<template>
·······
<el-button @click="searchEvent">查询</el-button>
·······
</template>
<script>
import {debounce} from "../utils"
export default {
methods: {
searchEvent () {
// 查看参数是否对
this.initData()
},
initData: debounce(() => {
// ······todo something
}, 500)
}
}
</script>
乍一看好像很ok啊,可以用啊。但是如果我想更新数据的同时不延时呢?
情景二,初始化请求不防抖:
如果我想在初始化的时候也查询一次initData呢?
······
import {debounce} from "../utils"
export default {
mounted () {
this.initData()
},
methods: {
searchEvent () {
// 查看参数是否对
this.initData()
},
initData: debounce(() => {
// ······todo something
}, 500)
}
}
</script>
这个时候初始化难道还要再等0.5秒吗?初页面如果慢个0.5秒对用户的体验是差很多的。 所以一般原来会怎么做?应该很快能想到吧?再写一个方法,可以直接调用的。
······
import {debounce} from "../utils"
export default {
mounted () {
this.initDataRunNow()
},
methods: {
searchEvent () {
// 查看参数是否对
this.initDataDebounce()
},
initDataDebounce: debounce(() => {
this.initDataRunNow()
}, 500),
initDataRunNow () {
// ······todo something
}
}
}
</script>
我相信看到这里头就不开心了。虽然能解决问题,但是我要改好多代码啊!
一个地方调整一个方法,那其他按钮都要加防抖呢?我咋整。同时可以看到这个地方debounce的时间是固定的500ms,如果其他需求是要不同情况下200ms,500ms,1000ms那岂不是得写很多方法?这嵌入防抖的代价也太大了吧。 基于以上需求我们将代码进行优化修改。
优化debounce方法
先上用例:
方法加上debounce功能
<template>
·······
<el-button @click="searchEvent">查询</el-button>
·······
</template>
<script>
import {debounceRun} from "../utils"
export default {
mounted () {
this.initData()
},
methods: {
searchEvent () {
// 查看参数是否对
debounceRun(this.initData)
},
initData () {
// ······todo something
}
}
}
</script>
看起来是不是简单了许多,只要在需要debounce的地方调用一下debounceRun就可以自动实现防抖。
支持方法传参
看到这里有小伙伴说了。啊你这如果有参数怎么办呢?那加上参数就好了。
searchEvent () {
// 查看参数是否对
debounceRun(this.initData, [a, b])
},
initData (a, b) {
// ······todo something
}
支持设置延时
又有人看到了说,啊你这防抖时间都没有自定义设置,真垃圾。那当然得支持啦,加上时间参数。
searchEvent () {
// 查看参数是否对
debounceRun(this.initData, [a, b], {wait: 450})
},
initData (a, b) {
// ······todo something
}
现在我相信会有小伙伴心动了。那么我就上代码啦:
const debounceRun = (() => {
// 储存方法的timer的map
let timerMap = new Map()
return (func, args = [], options = {}) => {
let defaultOptions = {
funcKey: null,
wait: 500,
throttle: false
}
options = Object.assign(defaultOptions, options)
// 如果没有mapKey 那么直接使用func作为map的key
let mapKey = options.funcKey || func
// 先看看map里面是否有timer,有timer代表之前调用过
let timer = timerMap.get(mapKey)
if (timer) {
if (options.throttle) return
else clearTimeout(timer)
}
timer = setTimeout(() => {
// 先把这个方法从map里面删掉
timerMap.delete(mapKey)
func.apply(this, args)
}, options.wait)
// 将方法的timer村进map, key是mapKey
timerMap.set(mapKey, timer)
}
})()
通过以上方法可以直接进行方法防抖,同时不需要生成中间方法。
其他用法:
节流(throttle)用法:
debounceRun(this.initData, args, {wait: 250, throttle: true})
通过funcKey改造原方法
如果initData方法已经在项目其他地方用了很多了可以直接调整initData,这样就不需要每个initData的地方都加上debounceRun。
initData () {
debounceRun(() => {
// ······todo something
}, { wait: 500, funcKey: this.initData })
}
以上使用funcKey是必须的,因为箭头方法是每次生成一次,所以使用的时候map的key就是不相等的。所以得使用funcKey说明这两个方法是属于一个。funcKey也可以是字符串,也可以是其他的对象,只要是唯一的就行。
funcKey参数妙用
使用funcKey参数也可以实现见两个方法只调用一个的效果。如:
let funcA = () => {console.log('funcA')}
let funcB = () => {console.log('funcB')}
不使用funcKey
debounceRun(func1)
debounceRun(func2)
输出:
funcA
funcB
使用funcKey
debounceRun(func1, [], {funcKey: 'mix_func_a_b'})
debounceRun(func2, [], {funcKey: 'mix_func_a_b'})
输出:
funcB
成功实现只有funcB输出而funcA不输出了
结尾
那么debounce的plus版本就算完成了。可以最小代码修改加入防抖/节流效果。 如果你觉得对你有帮助的话给一下赞咯~
就是听说点赞会加薪、升职、变帅、变美欸~
作者的其他文章: