1. 1. 场景
    1. 1.1. 节流的常见场景
    2. 1.2. 防抖的常见场景
  2. 2. 问题
  3. 3. 解决方法
    1. 3.1. 节流的代码解决
    2. 3.2. 防抖的代码解决
  4. 4. 概念
  5. 5. 优化
    1. 5.1. 对函数节流的通用封装
    2. 5.2. 对函数防抖的通用封装

场景

节流的常见场景

在日常开发中,我们可能会遇见这么一种场景,一个元素的宽高需要跟随窗口的变化来变化;例如:当我们使用 element-UI 的表格组件时,表格的数据是不确定的,但我们需要固定表头,当内容过多时可以对内容进行滚动,这时我们就需要给组件提供一个具体的高度,但是浏览器的窗口宽高是会变化的(人为的去缩放浏览器),这就要求我们去监听窗口的变化,当窗口宽高变化的时候,执行一个函数,重新计算浏览器宽高,让表格的高度发生变化

防抖的常见场景

日常开发过程中,我们可能会遇到实时搜索的场景;例如:用户在搜索框输入了内容,我们要根据内容实时的给用户推荐相关的搜索语,让用户选择。

问题

在上述两个场景中,有一个共同点就是函数被高频的调用,无论是实时窗口变化还是输入框的实时变化,都会超高频的触发 resizeinput 事件,但不同的是,在节流的场景中,我们更希望的是在一定的时间段,计算窗口宽高的函数只被执行一次;在防抖的场景中,我们更希望用户输入结束后,再根据输入内容,给用户推荐搜索语。

解决方法

节流的代码解决

对于节流的场景,我们的目标是在一定的时间段内,函数只被执行一次;我们假设在窗口变化过程中,每200毫秒计算一次浏览器宽高,也就是每200毫秒修改一次元素大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

const getWindowHeight = () => {
const {width,height} = window.screen;
console.log('浏览器宽度为:',width, '浏览器高度为:', height);
}
// 计算窗口宽高的变化
const throttle = (fn) => {
let timeNo;
return () => {
console.log('窗口变化');
if(timeNo) return;
timeNo = setTimeout(() => {
fn()
clearTimeout(timeNo);
timeNo = null;
}, 200);
}
}
// 监听窗口宽高变化
window.addEventListener('resize', throttle(getWindowHeight))

防抖的代码解决

对于防抖的场景,我们的目标是,在高频被触发时,以最后一次触发,去执行函数;我们假定时间间隔是200毫秒,也就是说用户在搜索框输入内容触发 input 事件后,等200毫秒后再去拿输入框内容,在这200毫秒内用户输入了,我们就重新计时等待;

1
2
3
4
5
6
7
8
9
10
11
12
13
const input = document.querySelector('#input'); // 拿输入框对象
const debounce = () => {
let timeNo;
return (e) => {
clearTimeout(timeNo);
timeNo = setTimeout(() => {
const {target: {value}} = e;
console.log('输入框的值为',value)
clearTimeout(timeNo);
},200)
}
}
input.addEventListener('input', debounce()); // 监听输入框的input事件

概念

函数节流 即在一个事件被连续的触发过程中,以一个固定的时间长度,间隔的去执行一个函数;

函数防抖 即在一个事件被连续的触发过程中,只在最后一次触发时执行一个函数

优化

对函数节流的通用封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* 通用版节流函数
*
* @param {function} fn - 要被执行的方法, 相隔多长时间要被执行的方法
* @param {number} Intervals - 间隔时间, 相隔多长时间调用一次对应方法
* @params {any} args - 剩余参数,剩余参数将会在调用fn时作为参数传给fn
* @params {any} params - 以下方使用例子看,resize事件被触发的时候,会在传个event对象过去,所以同样需要接收
* @return function
*/
const throttle = (fn, Intervals, ...args) => {
let timeNo;
return (...params) => {
if(timeNo) return;
timeNo = setTimeout(() => {
fn(...args,...params)
clearTimeout(timeNo);
timeNo = null;
}, Intervals);
}
}
// 使用例子
const obj = {
name: '张三',
fun(params, str) {
console.log(this.name, '接收参数', params, str);
}
}
window.addEventListener('resize', throttle(obj.fun.bind(this), 200, {text: '最外面传的第一个参数'}, '最外面传的第二个参数'))

对函数防抖的通用封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 通用版防抖函数
*
* @param {function} fn - 要被执行的方法, 相隔多长时间要被执行的方法
* @param {number} Intervals - 间隔时间, 相隔多长时间调用一次对应方法
* @params {any} args - 剩余参数,剩余参数将会在调用fn时作为参数传给fn
* @params {any} params - 以下方使用例子看,input事件被触发的时候,会在传个event对象过去,所以同样需要接收
* @return function
*/
const debounce = (fn, Intervals, ...args) => {
let timeNo;
return (...params) => {
clearTimeout(timeNo);
timeNo = setTimeout(() => {
fn(...args,...params)
clearTimeout(timeNo);
}, Intervals)
}
}
// 使用例子
const getInputValue = (e) => {
const {target: {value}} = e;
console.log('输入框的值为',value)
}
const input = document.querySelector('#input'); // 拿输入框对象
input.addEventListener('input', debounce(getInputValue,200)); // 监听输入框的input事件