JS防抖和节流
防抖
原理
事件响应函数在一段时间后才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定的时间内没有再次调用该函数,则执行响应逻辑
underscore中的debounce函数可以防抖
应用场景
- scroll事件滚动触发的时候
- 搜索框输入查询的时候
- 表单验证
- 按钮的提交事件
- 浏览器的窗口缩放,resize事件
自定义防抖函数
/**
* 防抖函数的自定义实现
* @param func 执行的函数
* @param wait 等待的时间
* @param immediate 是否立即执行
*/
function debounce (func, wait, immediate) {
// result -- 返回值
let timeout, result;
let debounced = function () {
let self = this
let args = arguments
clearTimeout(timeout)
if (immediate) {
// callNow是立即执行的变量
let callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
// 由于最开始timeout为undefined,取反为true,故立即执行
if (callNow) result = func.apply(self, args)
} else {
// 不会立即执行
timeout = setTimeout(function() {
//解决执行函数内部this指向问题以及event指向问题
result = func.apply(self, args)
}, wait)
}
return result
}
// 取消
debounced.cancel = function () {
clearTimeout(timeout)
timeout = null // 防止内存泄露
}
return debounced
}
节流
原理
如果你持续的触发事件,每隔一段时间,只执行一次事件
应用场景
- DOM元素的拖拽功能的实现
- 计算鼠标移动的距离
- 监听scroll滚动事件
初步实现
/**
* 节流函数 -- 初步实现 -- 第一次会触发,最后一次不会触发
* @param func 执行的函数
* @param wait 等待的时间
*/
function trottle (func, wait) {
let context, args
// 之前的时间戳
let old = 0 // 默认为0
return function () {
context = this
args = arguments
// 获取当前的时间戳
let now = new Date().valueOf()
if (now-old > wait) {
// 立即执行
func.apply(context, args)
old = now
}
}
}
改进版本
/**
* 节流函数 -- 第一次不会触发,最后一次会触发
*/
function trottle (func, wait) {
let context, args, timeout
return function () {
context = this
args = arguments
if (!timeout) {
timeout = setTimeout(() => {
timeout = null
func.apply(context, args)
}, wait)
}
}
}
再次改进版本
/**
* 节流函数
* 第一次会触发,最后一次会触发
*/
function trottle (func, wait) {
let context, args, timeout
let old = 0 // 时间戳
let later = function () {
old = new Date().valueOf()
timeout = null
func.apply(context, args)
}
return function () {
context = this
args = arguments
let now = new Date().valueOf()
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
func.apply(context, args)
old = now
}
if (!timeout) {
timeout = setTimeout(later, wait)
}
}
}
最终版本
/**
* 节流函数
* 最终版本
* 第一次不会触发,最后一次会调用 leading:false, trailing:true
* 第一次会触发,最后一次不会触发 leading:true, trailing:false
*/
function trottle (func, wait, options) {
let context, args, timeout, result
let old = 0 // 时间戳
if (!options) options = {}
let later = function () {
old = new Date().valueOf()
timeout = null
result = func.apply(context, args)
}
let trottled = function () {
context = this
args = arguments
let now = new Date().valueOf()
if (options.leading === false && !old) {
old = now;
}
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
result = func.apply(context, args)
old = now
}
if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, wait)
}
return result
}
trottled.cancel = function () {
clearTimeout(timeout)
timeout = null
}
return trottled
}