前言
最近在家里上班有点闲,就刷了些前端面试题,研究一下函数节流和函数防抖。这东西在项目中也常用,但一直是直接从网上copy代码,没深究过,最近就仔细看了下,还是有点收获的,前端面试的时候,常会被问道闭包的知识,节流与防抖就是一个很经典的闭包的应用。
场景
节流的常见场景
在日常开发中,我们可能会遇见这么一种场景,一个元素的宽高需要跟随窗口的变化来变化;例如:当我们使用 element-UI 的表格组件时,表格的数据是不确定的,但我们需要固定表头,当内容过多时可以对内容进行滚动,这时我们就需要给组件提供一个具体的高度,但是浏览器的窗口宽高是会变化的(人为的去缩放浏览器),这就要求我们去监听窗口的变化,当窗口宽高变化的时候,执行一个函数,重新计算浏览器宽高,让表格的高度发生变化
防抖的常见场景
日常开发过程中,我们可能会遇到实时搜索的场景;例如:用户在搜索框输入了内容,我们要根据内容实时的给用户推荐相关的搜索语,让用户选择。
问题
在上述两个场景中,有一个共同点就是函数被高频的调用,无论是实时窗口变化还是输入框的实时变化,都会超高频的触发 resize
和 input
事件,但不同的是,在节流的场景中,我们更希望的是在一定的时间段,计算窗口宽高的函数只被执行一次;在防抖的场景中,我们更希望用户输入结束后,再根据输入内容,给用户推荐搜索语。
解决方法
节流的代码解决
对于节流的场景,我们的目标是在一定的时间段内,函数只被执行一次;我们假设在窗口变化过程中,每200毫秒计算一次浏览器宽高,也就是每200毫秒修改一次元素大小
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毫秒内用户输入了,我们就重新计时等待;
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事件
复制代码
概念
函数节流 即在一个事件被连续的触发过程中,以一个固定的时间长度,间隔的去执行一个函数;
函数防抖 即在一个事件被连续的触发过程中,只在最后一次触发时执行一个函数
优化
对函数节流的通用封装
/**
* 通用版节流函数
*
* @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: '最外面传的第一个参数'}, '最外面传的第二个参数'))
复制代码
对函数防抖的通用封装
/**
* 通用版防抖函数
*
* @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事件
复制代码
下篇文章: 自定义Vue-Cli项目模板
iframe架构微前端实战 中说了,为了方便之后新开项目,父项目和子项目就可以做成 Vue模板,用Vue-Cli去创建项目时直接去拉取指定的模板,省却再进行认为修改的过程,直接开箱即用,下篇文章计划写如何自定义Vue项目模板。