防抖
概念
- 在n秒后执行事件响应函数,如果在n秒之内再次触发,将重新计算时间
- (加入立即执行参数)第一次触发会立即执行一次,在n秒之内不再触发,才能继续触发执行
应用场景
- scroll事件滚动触发
- 搜索框输入查询
- 表单验证
- 按钮提交事件
- 浏览器窗口缩放,resize事件
防抖函数中要实现的内容(源码中)
- 实现防抖功能
- this需要指向调用者
- event事件对象的指向问题
- 增加立即执行的参数
- 函数中出现返回值
- 取消防抖
代码实现
基础版
// 防抖
// 1 实现防抖功能
// 2 this需要指向调用者
// 3 event事件对象的指向问题
function debounce(fn, wait) {
let timer
return function () {
// 保存当前this,方便在定时器函数中修改this指向(定时器函数中的this指向window),这时this指向调用者;
let context = this
// arguments 是一个对应于传递给函数的参数的类数组对象(拿到传递给函数的所有参数并转化为数组对象)
let args = arguments
// 这里要注意clearTimeout会清除定时器(根据定时器的ID值进行清除),但不会影响由setTimeout返回的整数值timer,即下面一行代码执行后,timer仍然有值!!!
if (timer) clearTimeout(timer)
timer = setTimeout(function () {
fn.apply(context, args)
}, wait)
}
}
立即执行版
// 防抖
// 1 实现防抖功能
// 2 this需要指向调用者
// 3 event事件对象的指向问题
// 4 增加立即执行的参数
// 5 函数中出现返回值
function debounce(fn, wait, immediate) {
let timer, result
return function () {
// 保存当前this,方便在定时器函数中修改this指向(定时器函数中的this指向window),这时this指向调用者;
let context = this
// arguments 是一个对应于传递给函数的参数的类数组对象(拿到传递给函数的所有参数并转化为数组对象)
let args = arguments
// 这里要注意clearTimeout会清除定时器(根据定时器的ID值进行清除),但不会影响由setTimeout返回的整数值timer,即下面一行代码执行后,timer仍然有值!!!
if (timer) clearTimeout(timer)
if (immediate) {
// 第一次触发,会立即执行一次,在n秒之内不再触发,才能继续触发执行
let flag = !timer
if (flag) result = fn.apply(context, args)
timer = setTimeout(() => {
// 等待wait时间后,将timer置空,这时就可以再次触发并立即执行
timer = null
}, wait)
} else {
timer = setTimeout(function () {
result = fn.apply(context, args)
}, wait)
}
return result
}
}
防抖的升级
在wait之内触发debounce n次之后立刻执行fn-----多益网络笔试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button>点击</button>
<script>
let btn = document.querySelector('button')
// 防抖
btn.onclick = debounce(fn, 5000, 2)
function fn() {
console.log('打印')
}
// 在wait之内触发debounce n次之后立刻执行fn
let count = 0
function debounce(fn, wait, n) {
let timer
return function () {
let context = this
let args = arguments
if (timer) {
count++
clearTimeout(timer)
if (count === n) {
fn.apply(context, args)
count = 0
return
}
}
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
}
</script>
</body>
</html>
节流
概念
在n秒之内只执行一次事件处理函数,无论在n秒之内触发多少次
应用场景
- DOM元素的拖拽功能实现
- 射击游戏
- 计算鼠标移动距离
- 监听scroll滚动事件
代码实现
时间戳实现
// 1 时间戳实现
// 第一次可以立即执行,最后一次不会自动触发执行
function throttle(fn, wait) {
let context, args
let old = 0
return function () {
// 保存当前this,方便修改fn中的this指向
// !!! 如果不进行this指向修改,fn()中的this会指向window,这里是为什么???现在还不太懂
context = this
args = arguments
// 获取当前时间戳
let now = new Date().valueOf()
// 第一次一定执行,这样就实现了立即执行,再以后wait内只执行一次
if (now - old > wait) {
fn.apply(context, args)
old = now
}
}
}
定时器实现
// 2 定时器实现
// 第一次不会立即执行,最后一次会自动触发执行
function throttle(fn, wait) {
let context, args, timer
return function () {
context = this
args = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(context, args)
}, wait)
}
}
}
定时器和时间戳实现
// 3 结合时间戳和定时器的版本
// 第一次立即执行,最后一次会自动触发执行
function throttle1(fn, wait) {
let context, args, timer
let old = 0
return function () {
context = this
args = arguments
let now = new Date().valueOf()
// 只在第一次触发时立即执行一次
if (now - old > wait) {
// 下面这个判断为什么,还有点不懂
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(context, args)
old = now
}
if (!timer) {
timer = setTimeout(() => {
timer = null
old = new Date().valueOf()
fn.apply(context, args)
}, wait)
}
}
}