这两个东西都以闭包的形式存在。
防抖(debounce)
- 事件被触发n秒后再执行回调,如果在这n秒内又被调用,则重新计时。
节流(throttle)
- 在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。(相当于间隔n秒执行一次,间隔n秒执行一次......)
防抖(debounce)
使用场景:
- scroll 事件滚动触发
- 搜索框输入查询
- 表单验证
- 按钮提交事件
- 浏览器窗口缩放
1. 非立即执行回调的防抖
<div>debounce:<input type="text" id="debounce-input" /></div>
<script>
const inputDom = document.getElementById('debounce-input')
function debounce(func, wait) {
let timeout
// let result; // 如果需要返回值,在这里定义一个变量
return function () {
const that = this // 改变执行函数内部 this 的指向
const args = arguments // 解决 doSomeThing event指向问题
clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(that, args)
//需要函数的返回值,在这里给result赋值
// result = func.apply(that, args)
}, wait)
// return result;
}
}
function doSomeThing(e) {
console.log('我是防抖~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
inputDom.onkeyup = debounce(doSomeThing, 300)
</script>
2. 立即执行回调的防抖
<div>debounce:<input type="text" id="debounce-input" /></div>
<script>
const inputDom = document.getElementById('debounce-input')
function debounce(func, wait, immediate) {
// immediate 是否立即执行
let timeout
return function () {
const that = this // 改变执行函数内部 this 的指向
const args = arguments // 解决 doSomeThing event指向问题
clearTimeout(timeout) // 每次进来先清除上一次的 setTimeout
if (immediate) {
const callNow = !timeout //需要一个条件判断是否要去立即执行
timeout = setTimeout(function () {
timeout = null
}, wait)
// 立即执行
if (callNow) func.apply(that, args)
} else {
// 不会立即执行
timeout = setTimeout(function () {
func.apply(that, args)
}, wait)
}
}
}
function doSomeThing(e) {
console.log('我是防抖~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
inputDom.onkeyup = debounce(doSomeThing, 300, true)
</script>
3. 取消防抖
<div>
debounce:<input type="text" id="debounce-input" />
<button id="cancel-btn">取消防抖</button>
</div>
<script>
const inputDom = document.getElementById('debounce-input')
const cancelBtnDom = document.getElementById('cancel-btn')
function debounce(func, wait) {
let timeout
let debounced = function () {
const that = this // 改变执行函数内部 this 的指向
const args = arguments // 解决 doSomeThing event指向问题
clearTimeout(timeout)
timeout = setTimeout(function () {
func.apply(that, args)
}, wait)
}
debounced.cancel = function () {
// 新增取消方法
clearTimeout(timeout)
timeout = null
}
return debounced
}
function doSomeThing(e) {
console.log('我是防抖~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
const doDebounce = debounce(doSomeThing, 1000, true)
inputDom.onkeyup = doDebounce
cancelBtnDom.onclick = function () {
doDebounce.cancel()
}
</script>
节流(throttle)
应用场景:
- 监听 scroll 滚动事件;
- DOM 元素的拖拽功能的实现;
- 射击游戏;
- 计算鼠标移动的距离;
1. 时间戳
<div style="height: 10000px"></div>
<script>
// 第一次立即执行,最后一次不会被调用触发执行
function throttle(func, wait) {
let old = 0 // 之前的时间戳
let throttled = function () {
const that = this
const args = arguments
let now = new Date().valueOf() // 获取当前时间戳
if (now - old > wait) {
func.apply(that, args) // 立即执行
old = now
}
}
// 取消节流
throttled.cancel = function() {
old = new Date()
}
return throttled
}
function doSomeThing(e) {
console.log('我是节流~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
document.onscroll = throttle(doSomeThing, 500)
</script>
2. 定时器
<div style="height: 10000px"></div>
<script>
// 第一次不立即执行,最后一次会被调用触发执行
function throttle(func, wait) {
let timeout
let throttled = function () {
const that = this
const args = arguments
if (!timeout) {
timeout = setTimeout(function () {
func.apply(that, args)
timeout = null
}, wait)
}
}
// 取消节流
throttled.cancel = function() {
clearTimeout(timeout)
timeout = null
}
return throttled
}
function doSomeThing(e) {
console.log('我是节流~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
document.onscroll = throttle(doSomeThing, 500)
</script>
3. 时间戳 + 定时器
<div style="height: 10000px"></div>
<script>
// 第一次立即执行,最后一次会被调用触发执行
function throttle(func, wait) {
let timeout
let old = 0 // 之前的时间戳
return function () {
const that = this
const args = arguments
let now = new Date() // 获取当前时间戳
if (now - old > wait) {
// 第一次会立即执行
if (timeout) {
clearTimeout(timeout)
timeout = null
}
func.apply(that, args) // 立即执行
old = now
} else if (!timeout) {
// 最后一次会执行
timeout = setTimeout(function () {
func.apply(that, args)
old = new Date()
timeout = null
}, wait)
}
}
}
function doSomeThing(e) {
console.log('我是节流~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
document.onscroll = throttle(doSomeThing, 500)
</script>
4. 优化
能够通过参数让自己能取决立即执行或最后一次执行
<div style="height: 10000px"></div>
<script>
function throttle(func, wait, options) {
let timeout
let old = 0 // 之前的时间戳
if (!options) options = {}
return function () {
const that = this
const args = arguments
let now = new Date().valueOf() // 获取当前时间戳
if (options.leading === false && !old) { // 让第一次不执行
old = now
}
if (now - old > wait) {
// 第一次会立即执行
if (timeout) {
clearTimeout(timeout)
timeout = null
}
func.apply(that, args) // 立即执行
old = now
} else if (!timeout && options.trailing !== false) {
// 最后一次会执行
timeout = setTimeout(function () {
func.apply(that, args)
old = new Date().valueOf()
timeout = null
}, wait)
}
}
}
function doSomeThing(e) {
console.log('我是节流~~~')
// console.log(e)
// console.log(this);
// 可能会做 回调 或者 ajax 请求
}
/*
* 第一次会立即执行,最后一次不会被调用 {leading:true,trailing:false}
* 第一次不会立即执行,最后一次会被调用 {leading:false,trailing:true}
* 第一次会立即执行,最后一次会被调用 {leading:true,trailing:true}
* options = { leading:xxx,trailing:xxx }; 默认 options 为 {leading:true,trailing:true}
* throttle(doSomeThing,wait,options)
*/
document.onscroll = throttle(doSomeThing, 500)
</script>