实现
function debounce(func, wait, immediate) { // 形参赋值默认值,会引起麻烦机制,所以不推荐
// 多个参数及传递和不传递的默认处理
if (typeof func !== 'function') throw new TypeError('func must be a function');
if (typeof wait === 'undefined') wait = 500;
if (typeof wait === 'boolean') {
immediate = wait
wait = 500
}
if (typeof immediate !== 'boolean') immediate = true;
// 是否需要立即执行首次
// 是:可不必定时器,去触发延迟执行:「此处同节流无需定时器情况」一般适用于防止按钮连点
if (immediate) {
// 方式一:无需定时器函数
previous = 0; // 记录上一次操作的时间
return function proxy(...params) {
let self = this,
now = +new Date();// 当前这次触发操作的时间
// 判断是否需要执行
if (
previous === 0 // 第一次执行(上次执行时间为默认值0)
|| now >= previous + wait // 或超过限制时间节点(当前时间>=上次执行时间+延迟时间)时
) {
previous = now
func.call(self, ...params)
}
}
}
// 否:一般适用于‘输入模糊搜索’事件,需要输入完成后,最后一次响应
else {
let timer = null
return function proxy(...params) {
let self = this // 保证原始函数的形参和this执行
clearTimeout(timer)
timer = setTimeout(function () {
func.call(self, ...params)
}, wait)
}
}
}
/*
<button type="submit" class='submit'>submit</button>
<input placeholder="模糊搜索" class="inputQuerySearch">
*/
// 去抖场景1:防止按钮连点
const submit = document.querySelector('.submit')
function handle() {
// 具体在点击的时候要处理的业务
console.log('ok')
}
submit.onclick = debounce(handle, true)
// 去抖场景2:按钮点击
const input = document.querySelector('.inputQuerySearch')
function handleInputChange() {
// 具体在点击的时候要处理的业务
console.log('开始模糊搜索...')
}
input.addEventListener('input', debounce(handleInputChange, false))
推导
function debounce(func, wait, immediate) { // 形参赋值默认值,会引起麻烦机制,所以不推荐
// 多个参数及传递和不传递的默认处理
if (typeof func !== 'function') throw new TypeError('func must be a function');
if (typeof wait === 'undefined') wait = 500;
if (typeof wait === 'boolean') {
immediate = wait
wait = 500
}
if (typeof immediate !== 'boolean') immediate = false;
// 设定定时器返回标识
let timer = null
return function proxy(...params) {
let self = this // 保证原始函数的形参和this执行
// 是否需要立即执行首次
// 是:可不必定时器,去触发延迟执行
if (immediate) {
// 是否设置过定时器标识(设置后把定时器标识timer为数字),来截断函数执行,
// 是:退出,不做任何处理
if (timer) return;
// 否:没有设置过,则立即执行,且设置定时器标识截断
func.call(self, ...params)
timer = setTimeout(function () {
timer = null // 把定时器标识重置为null
}, wait)
}
// 否:一般适用于‘输入模糊搜索’事件,需要输入完成后,最后一次响应
else {
clearTimeout(timer)
timer = setTimeout(function () {
func.call(self, ...params)
}, wait)
}
}
}
分析过程
function debounce(func, wait, immediate) { // 形参赋值默认值,会引起麻烦机制,所以不推荐
// 多个参数及传递和不传递的默认处理
if (typeof func !== 'function') throw new TypeError('func must be a function');
if (typeof wait === 'undefined') wait = 500;
if (typeof wait === 'boolean') {
immediate = wait
wait = 500
}
if (typeof immediate !== 'boolean') immediate = false;
// 设定定时器返回标识
let timer = null
return function proxy(...params) {
let self = this // 保证原始函数的形参和this执行
// 是否需要立即执行首次
// 是:可不必定时器,去触发延迟执行
if (immediate) {
// 是否设置过定时器标识(设置后把定时器标识timer为数字),来截断函数执行,
// 是:退出,不做任何处理
if (timer) return;
// 否:没有设置过,则立即执行,且设置定时器标识截断
func.call(self, ...params)
timer = setTimeout(function () {
timer = null // 把定时器标识重置为null
}, wait)
}
// 否:一般适用于‘输入模糊搜索’事件,需要输入完成后,最后一次响应
else {
clearTimeout(timer)
timer = setTimeout(function () {
func.call(self, ...params)
}, wait)
}
}
}
/**
* 函数得防抖(debounce)和节流(throttle)
* 在”高频“触发的场景下,需要进行防抖和节流
* + 狂点一个按钮(开头触发)
* + 页面滚动
* + 输入模糊匹配(最后一次触发)
* + ...
* 我们自己设定,多长的时间内,触发两次即以上就算“高频”:封装方法的时候需要指定这个频率(可以设置默认值)
* 「防抖」在某一次高频触发下,我们只识别一次(可以控制开始触发,还是最后一次触发);详细:假设我们规定500ms触发多次算是高频,只要们检测到是高频触发了,则在本次频繁操作下(哪怕你操作了10min)也只触发一次
* 可以开始,也可以结尾触发,一般按钮点击倾向于开始节点处触发,这样没有延迟
* 「节流」在某一次高频触发下,我们不是只识别一次,按照我们设定的间隔时间(自己规定的频率),没有达到这个频率都会触发一次;详细:假如我们规定频率是500ms,我们操作了10min,触发的次数=(10*60*1000)/500
*/
const submit = document.querySelector('.submit')
console.log('submit---:', submit)
// submit.onclick = function () {
// console.log('ok')
// }
// widow.onscroll = function () {
// // 默认情况下:浏览器在最快的反应时间内(4~6ms),就会识别监听一次事件触发,把绑定的方法执行,这样导致方法执行的次数过多,造成不必要的资源浪费
// console.log('ok')
// }
// // 业务场景中处理的技巧1:标识判断
// let flag = false
// submit.onclick = function () {
// if (flag) return;
// flag = true
// console.log('ok')
// setTimeout(function () {
// // 事情处理完
// flag = false
// }, 1000)
// }
// // 业务场景中处理技巧2:按钮置灰,移除事件绑定
// function handle() {
// submit.onclick = null
// submit.disabled = true
// // ...
// console.log('ok')
// setTimeout(function () {
// submit.onclick = handle
// submit.disabled = false
// }, 1000)
// }
// submit.onclick = handle;
// lodash 实现
function debounce(func, wait, immediate) { // 形参赋值默认值,会引起麻烦机制,所以不推荐
// 多个参数及传递和不传递的默认处理
if (typeof func !== 'function') throw new TypeError('func must be a function');
if (typeof wait === 'undefined') wait = 500;
if (typeof wait === 'boolean') {
immediate = wait
wait = 500
}
if (typeof immediate !== 'boolean') immediate = false;
// 设定定时器返回标识
let timer = null
return function proxy(...params) {
let self = this // 保证原始函数的形参和this执行
// // 方式一:混合兼容
// let now = immediate && !timer;
// clearTimeout(timer)
// timer = setTimeout(function () {
// timer = null
// !immediate ? func.call(self, ...params) : null
// }, wait)
// // 第一次触发就立即执行
// now ? func.call(self, ...params) : null
// 方式二: 拆分immediate处理情况
// 是否立即执行
// 是:一般适用于‘按钮点击’事件,需要点击立即响应
if (immediate) {
// 是否设置过定时器标识(设置后把定时器标识timer为数字),来截断函数执行,
// 是:退出,不做任何处理
if (timer) return;
// 否:没有设置过,则立即执行,且设置定时器标识截断
func.call(self, ...params)
timer = setTimeout(function () {
timer = null // 把定时器标识重置为null
}, wait)
}
// 否:一般适用于‘输入模糊搜索’事件,需要输入完成后,最后一次响应
else {
clearTimeout(timer)
timer = setTimeout(function () {
func.call(self, ...params)
}, wait)
}
}
}
function handle() {
// 具体在点击的时候要处理的业务
console.log('ok')
}
submit.onclick = debounce(handle, true)
// submit.onclick=proxy; 疯狂点击的情况下,proxy会被疯狂执行,我们需要在proxy中根据频率管控handle的执行次数「函数柯里化」
// submit.onclick = handle; // handle-this:submit 传递一个事件对象
/**
* 过程分析
* 定时器目的,检测500ms内是否会触发第二次,如果有,则为高频触发
* 第一次proxy执行,设置一个定时器
* 4ms
* 第二次proxy执行,清除之前设定的,重新设定,再去检测500ms内是否有第二次触发
* ...
* 疯狂点击100次
* 之前99次定时器都清除了
* 只留最后一个
* 等到过了500ms 发现没有触发第二次 执行函数
*/