由于节流和防抖函数的实现都用到了闭包,所以在了解节流和防抖之前先简单介绍下什么是闭包。
function Add() {
var x = 1;
return function () {
x++;
console.log(x);
}
}
var result = Add(); //第一次执行A函数
result(); //2
result(); //3上述代码:result指向函数Add返回的函数,运行完Add()后,Add的执行环境被释放,但由于函数Add里返回的函数中有对变量x的引用,因此释放时不会释放掉x,每次调用result()时,都保持对x的引用,因此x一直再增加。

防抖函数
防抖的应用场景很多:
总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数;
在规定时间内,只让最后一次生效,前面的不生效。
<body>
<input type="text" class="search">
<script>
var timer
function debounce() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
ajax()
}, 1000);
}
function ajax() {
console.log('ajaxajaxajax');
}
var search = document.querySelector('.search')
search.addEventListener('input', debounce)
</script>
</body>上述代码是最简单的防抖函数,但是会出现如下问题: 1、对于一个页面上需要多个防抖函数的时候,需要写很多重复代码。不能复用 2、全局变量污染作用域
因此有以下升级版
<body>
<input type="text" class="search">
<script>
function debounce() {
var timer
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
ajax()
}, 1000);
}
}
function ajax() {
console.log('ajaxajaxajax');
}
var search = document.querySelector('.search')
search.addEventListener('input', debounce())
</script>
</body><body>
<input type="text" class="search">
<script>
// 优化可定义要执行的函数以及防抖时间
function debounce(fn, delay) {
var timer
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function () {
fn()
}, delay);
}
}
function ajax(arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); // window
console.log(arg1, arg2); // undefined undefined
}
var search = document.querySelector('.search')
search.addEventListener('input', debounce(ajax, 500))
</script>
</body><body>
<input type="text" class="search">
<script>
// 优化this和arguments
function debounce(fn, delay) {
var timer
return function () {
if (timer) {
clearTimeout(timer)
}
var _this = this
var _arguments = arguments
timer = setTimeout(function () {
fn.apply(_this, _arguments)
}, delay);
}
}
function ajax(e, arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); // <input type="text" class="search">
}
var search = document.querySelector('.search')
var debounceWrap = debounce(ajax, 500)
search.addEventListener('input', function () {
debounceWrap.apply(this, [event, 100, 200])
})
// 这样也可,但是要注意参数位置变化
// Arguments(3) [100, 200, InputEvent, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// search.addEventListener('input', debounceWrap.bind(search, 100, 200))
</script>
</body><body>
<input type="text" class="search">
<script>
// 优化头部立即执行
function debounce(fn, delay, leading) {
var timer
var leading = leading || false
return function () {
if (timer) {
clearTimeout(timer)
}
var _this = this
var _arguments = arguments
if (leading) {
// 通过一个变量来记录是否立即执行
var isFirst = false
// 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
if (!timer) {
fn.apply(_this, _arguments)
isFirst = true
}
// 其余延迟执行
timer = setTimeout(function () {
// 立即执行完第一次防抖后,timer就不是fasle了,
// 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
timer = null
// 判断在立即执行后,有没有继续触发防抖,有的话才执行,
// 如果只是执行了一次立即执行就结束了,则不执行
if (!isFirst) {
fn.apply(_this, _arguments)
}
}, delay);
} else {
timer = setTimeout(function () {
fn.apply(_this, _arguments)
}, delay);
}
}
}
function ajax(e, arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); // <input type="text" class="search">
}
var search = document.querySelector('.search')
var debounceWrap = debounce(ajax, 1000, true)
search.addEventListener('input', function () {
debounceWrap.apply(this, [event, 100, 200])
})
// 这样也可,但是要注意参数位置变化
// Arguments(3) [100, 200, InputEvent, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// search.addEventListener('input', debounce(ajax, 1000, true).bind(search, 100, 200))
</script>
</body><body>
<input type="text" class="search">
<button class="cancel">取消</button>
<script>
// 优化头部立即执行
function debounce(fn, delay, leading) {
var timer
var leading = leading || false
var debounceFn = function () {
if (timer) {
clearTimeout(timer)
}
var _this = this
var _arguments = arguments
if (leading) {
// 通过一个变量来记录是否立即执行
var isFirst = false
// 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
if (!timer) {
fn.apply(_this, arguments)
isFirst = true
}
// 其余延迟执行
timer = setTimeout(() => {
// 立即执行完第一次防抖后,timer就不是fasle了,
// 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
timer = null
// 判断在立即执行后,有没有继续触发防抖,有的话才执行,
// 如果只是执行了一次立即执行就结束了,则不执行
if (!isFirst) {
fn.apply(_this, arguments)
}
}, delay);
} else {
timer = setTimeout(() => {
fn.apply(_this, arguments)
}, delay);
}
}
debounceFn.cancel = function () {
clearTimeout(timer)
// 清空定时器后,timer还是一个值,会导致下次立即执行失效
// 因此清空定时器后,还要将timer设为null
// 上面步骤不能乱,要先清空,再重新赋值null
timer = null
}
return debounceFn
}
function ajax(e, arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); // <input type="text" class="search">
}
var search = document.querySelector('.search')
var debounceWrap = debounce(ajax, 1000, true)
// 注意:由于函数拥有自己的作用域,如果将防抖和中途取消分别进行定义,
// 则他们指向的不是同一个作用域,会导致中途取消功能取消失效
// 如果要使用中途取消功能,则必须要在外部定义一个变量保存防抖函数
// 在执行防抖和中途取消时,都得通过外部的全局变量进行操作!
search.addEventListener('input', function () {
debounceWrap.apply(this, [event, 100, 200])
})
// 这样也可,但是要注意参数位置变化
// Arguments(3) [100, 200, InputEvent, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// search.addEventListener('input', debounceWrap.bind(search, 100, 200))
// 这样防抖可以,但是无法进行中途取消,错误写法!
// search.addEventListener('input', debounce(ajax, 1000, true).bind(search, 100, 200))
var cancel = document.querySelector('.cancel')
cancel.addEventListener('click', function () {
debounceWrap.cancel()
})
// 这样取消也可
// cancel.addEventListener('click', debounceWrap.cancel)
// 这样中途取消会执行,但是取消的不是同一个防抖函数,错误写法!
// cancel.addEventListener('click', debounce(ajax, 1000, true).cancel)
</script>
</body><body>
<input type="text" class="search">
<button class="cancel">取消</button>
<script>
// 优化返回值
function debounce(fn, delay, option) {
var timer
var option = option || {}
var leading = option.leading || false
var callback = option.callback || null
var result
var debounceFn = function () {
if (timer) {
clearTimeout(timer)
}
var _this = this
var _arguments = arguments
if (leading) {
// 通过一个变量来记录是否立即执行
var isFirst = false
// 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
if (!timer) {
result = fn.apply(_this, arguments)
if (callback) {
callback(result)
}
isFirst = true
}
// 其余延迟执行
timer = setTimeout(() => {
// 立即执行完第一次防抖后,timer就不是fasle了,
// 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
timer = null
// 判断在立即执行后,有没有继续触发防抖,有的话才执行,
// 如果只是执行了一次立即执行就结束了,则不执行
if (!isFirst) {
result = fn.apply(_this, arguments)
if (callback) {
callback(result)
}
}
}, delay);
} else {
timer = setTimeout(() => {
result = fn.apply(_this, arguments)
if (callback) {
callback(result)
}
}, delay);
}
}
debounceFn.cancel = function () {
clearTimeout(timer)
// 清空定时器后,timer还是一个值,会导致下次立即执行失效
// 因此清空定时器后,还要将timer设为null
// 上面步骤不能乱,要先清空,再重新赋值null
timer = null
}
return debounceFn
}
function ajax(e, arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); // <input type="text" class="search">
return '我是ajax的返回值'
}
var search = document.querySelector('.search')
var debounceWrap = debounce(ajax, 1000, {
leading: true,
callback: function (res) {
console.log(res); // 我是ajax的返回值
}
})
// 注意:由于函数拥有自己的作用域,如果将防抖和中途取消分别进行定义,
// 则他们指向的不是同一个作用域,会导致中途取消功能取消失效
// 如果要使用中途取消功能,则必须要在外部定义一个变量保存防抖函数
// 在执行防抖和中途取消时,都得通过外部的全局变量进行操作!
search.addEventListener('input', function () {
debounceWrap.apply(this, [event, 100, 200])
})
var cancel = document.querySelector('.cancel')
cancel.addEventListener('click', function () {
debounceWrap.cancel()
})
</script>
</body><body>
<input type="text" class="search">
<button class="cancel">取消</button>
<script>
// 优化返回值
function debounce(fn, delay, leading) {
var timer
var leading = leading || false
var debounceFn = function () {
if (timer) {
clearTimeout(timer)
}
var _this = this
var _arguments = arguments
return new Promise((resolve, reject) => {
if (leading) {
// 通过一个变量来记录是否立即执行
var isFirst = false
// 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
if (!timer) {
resolve(fn.apply(_this, _arguments))
isFirst = true
}
// 其余延迟执行
timer = setTimeout(() => {
// 立即执行完第一次防抖后,timer就不是fasle了,
// 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
timer = null
// 判断在立即执行后,有没有继续触发防抖,有的话才执行,
// 如果只是执行了一次立即执行就结束了,则不执行
if (!isFirst) {
resolve(fn.apply(_this, _arguments))
}
}, delay);
} else {
timer = setTimeout(() => {
resolve(fn.apply(_this, _arguments))
}, delay);
}
})
}
debounceFn.cancel = function () {
clearTimeout(timer)
// 清空定时器后,timer还是一个值,会导致下次立即执行失效
// 因此清空定时器后,还要将timer设为null
// 上面步骤不能乱,要先清空,再重新赋值null
timer = null
}
return debounceFn
}
function ajax(e, arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); // <input type="text" class="search">
return 100
}
var search = document.querySelector('.search')
var debounceWrap = debounce(ajax, 1000, true)
// 注意:由于函数拥有自己的作用域,如果将防抖和中途取消分别进行定义,
// 则他们指向的不是同一个作用域,会导致中途取消功能取消失效
// 如果要使用中途取消功能,则必须要在外部定义一个变量保存防抖函数
// 在执行防抖和中途取消时,都得通过外部的全局变量进行操作!
search.addEventListener('input', function () {
debounceWrap.apply(this, [event, 100, 200]).then(res => {
console.log(res); //100
})
})
var cancel = document.querySelector('.cancel')
cancel.addEventListener('click', function () {
debounceWrap.cancel()
})
</script>
</body><body>
<input type="text" class="search">
<button class="cancel">取消</button>
<script>
function debounce(fn, delay, leading) {
var timer
var leading = leading || false
var debounceFn = function () {
if (timer) {
clearTimeout(timer)
}
var _this = this
var _arguments = arguments
return new Promise((resolve, reject) => {
if (leading) {
var isFirst = false
if (!timer) {
resolve(fn.apply(_this, _arguments))
isFirst = true
}
timer = setTimeout(() => {
timer = null
if (!isFirst) {
resolve(fn.apply(_this, _arguments))
}
}, delay);
} else {
timer = setTimeout(() => {
resolve(fn.apply(_this, _arguments))
}, delay);
}
})
}
debounceFn.cancel = function () {
clearTimeout(timer)
timer = null
}
return debounceFn
}
function ajax(e, arg1, arg2) {
console.log('ajaxajaxajax');
// Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
console.log(arguments);
console.log(this); //<input type="text" class="search">
return 100
}
var search = document.querySelector('.search')
var debounceWrap = debounce(ajax, 1000, true)
search.addEventListener('input', function () {
debounceWrap.apply(this, [event, 100, 200]).then(res => {
console.log(res); //100
})
})
var cancel = document.querySelector('.cancel')
cancel.addEventListener('click', function () {
debounceWrap.cancel()
})
</script>
</body><body>
<input type="text" class="search">
<script>
function throttle(fn, interval) {
var lastTime = 0
return function () {
var _this = this
var _arguments = arguments
var newTime = new Date().getTime()
if (newTime - lastTime > interval) {
fn.apply(_this, _arguments)
lastTime = newTime
}
}
}
// 获取输入框
var search = document.querySelector('.search');
// 监听事件
var counter = 0;
function searchFunc(event) {
counter++;
console.log("发送" + counter + "次网络请求");
console.log(this);
};
search.addEventListener('input', throttle(searchFunc, 1000))
</script>
</body><body>
<input type="text" class="search">
<script>
function throttle(fn, interval, trailing) {
var lastTime = 0
var timer
var trailing = trailing || false
return function () {
var _this = this
var _arguments = arguments
var newTime = new Date().getTime()
clearTimeout(timer)
if (newTime - lastTime > interval) {
fn.apply(_this, _arguments)
lastTime = newTime
} else if (trailing) {
timer = setTimeout(() => {
fn.apply(_this, _arguments)
}, interval);
}
}
}
// 获取输入框
var search = document.querySelector('.search');
// 监听事件
var counter = 0;
function searchFunc(event) {
counter++;
console.log("发送" + counter + "次网络请求");
console.log(this);
};
search.addEventListener('input', throttle(searchFunc, 1000, true))
</script>
</body><body>
<input type="text" class="search">
<script>
function throttle(fn, interval, option) {
var lastTime = 0
var timer
var option = option || {}
var trailing = option.trailing || false
var callback = option.callback || null
return function () {
var _this = this
var _arguments = arguments
// 获取当前最新的时间戳
var newTime = new Date().getTime()
// 只要触发事件就清除定时器
if (timer) {
clearTimeout(timer)
}
var result
if (newTime - lastTime > interval) {
result = fn.apply(_this, _arguments)
if (callback) {
callback(result)
}
lastTime = newTime
} else if (trailing) {
timer = setTimeout(() => {
result = fn.apply(_this, _arguments)
if (callback) {
callback(result)
}
}, interval);
}
}
}
// 获取输入框
var search = document.querySelector('.search');
// 监听事件
var counter = 0;
function searchFunc(event) {
counter++;
console.log("发送" + counter + "次网络请求");
console.log(this);
return 100
};
search.addEventListener('input', throttle(searchFunc, 1000, {
trailing: true,
callback: function (res) {
console.log(res); // 100
}
}))
// var funWrap = throttle(searchFunc, 1000, {
// trailing: true,
// callback: function (res) {
// console.log(res);
// return res
// }
// })
// search.addEventListener('input', function () {
// funWrap.call(this)
// })
</script>
</body><body>
<input type="text" class="search">
<script>
function throttle(fn, interval, option) {
var lastTime = 0
var timer
var option = option || {}
var trailing = option.trailing || false
return function () {
var _this = this
var _arguments = arguments
// 获取当前最新的时间戳
var newTime = new Date().getTime()
// 只要触发事件就清除定时器
if (timer) {
clearTimeout(timer)
}
var result
return new Promise((resolve, reject) => {
if (newTime - lastTime > interval) {
result = fn.apply(_this, _arguments)
resolve(result)
lastTime = newTime
} else if (trailing) {
timer = setTimeout(() => {
result = fn.apply(_this, _arguments)
resolve(result)
}, interval);
}
})
}
}
// 获取输入框
var search = document.querySelector('.search');
// 监听事件
var counter = 0;
function searchFunc(event) {
counter++;
console.log("发送" + counter + "次网络请求");
console.log(this);
return 100
};
// search.addEventListener('input', throttle(searchFunc, 1000, {
// trailing: true,
// }))
var funWrap = throttle(searchFunc, 1000, {
trailing: true,
})
search.addEventListener('input', function () {
funWrap.call(this).then(res => {
console.log(res);
})
})
</script>
</body><body>
<input type="text" class="search">
<script>
function throttle(fn, interval, option) {
var lastTime = 0
var timer
var option = option || {}
var trailing = option.trailing || false
return function () {
var _this = this
var _arguments = arguments
var newTime = new Date().getTime()
if (timer) {
clearTimeout(timer)
}
var result
return new Promise((resolve, reject) => {
if (newTime - lastTime > interval) {
result = fn.apply(_this, _arguments)
resolve(result)
lastTime = newTime
} else if (trailing) {
timer = setTimeout(() => {
result = fn.apply(_this, _arguments)
resolve(result)
}, interval);
}
})
}
}
// 获取输入框
var search = document.querySelector('.search');
// 监听事件
var counter = 0;
function searchFunc(event) {
counter++;
console.log("发送" + counter + "次网络请求");
console.log(this);
return 100
};
// search.addEventListener('input', throttle(searchFunc, 1000, {
// trailing: true,
// }))
var funWrap = throttle(searchFunc, 1000, {
trailing: true,
})
search.addEventListener('input', function () {
funWrap.call(this).then(res => {
console.log(res);
})
})
</script>
</body>