防抖与节流
防抖节流优化高频率执行代码的一种手段。
当函数绑定一些持续触发的事件如:resize、scroll、mousemove ,键盘输入,多次快速click等等,如果每次触发都要执行一次函数,会带来性能下降,资源请求太频繁等问题,为了解决这些问题,防抖(debounce) 和 节流(throttle)就出现了;
比喻:
游戏中的回城就可以认为是防抖,在回城的读秒过程中,如果再次执行回城操作,那么会重新进行读秒,只有整个读秒过程都没有再次执行回城操作,那么等到读秒结束才能成功回城。
节流类似于技能cd,不管你按了多少次,必须等到cd结束后才能释放技能。也就是说在如果在cd时间段,不管你触发了几次事件,只会执行一次。只有当下一次cd转换,才会再次执行。
防抖
定义: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时;
简单版本:
//timeout是需要进行防抖处理的函数,第二个是延迟时间
function debounce(timeout, fn) {
//实现防抖函数的核心是使用setTimeout
//time变量用于保存setTimeout返回的Id
let _time = null
return function () {
//如果time不是0,定时器存在,将定时器清除
let _arg = arguments
clearTimeout(_time)
_time = setTimeout(() => {
fn.apply(this, _arg)
}, timeout)
}
}
示例:
<input type="text"/>
let int = document.querySelector("input");
let time = null; //初始化定时器
int.oninput = function(){
if(time != null){ //判断是否已存在定时器
clearTimeout(time) //清空定时器
}
time = setTimeout(() => {
console.log(this.value); //模拟业务逻辑
}, 500);
}
效果
上面防抖函数的实现,已经基本上可以实现防抖的效果,但是还是会有一点小问题,比如说this的指向和原来的函数是不一致的以及参数问题。将防抖函数与业务逻辑单独封装
let int = document.querySelector("input");
int.oninput = debounce(task,500)
//业务逻辑
function task(){
console.log(this.value);
}
//防抖函数
//封装一个名为debounce,且返回值为函数的防抖函数,来替代oninput后承接的内容。因为返回值为函数,所以用闭包思想来实现封装
function debounce(fn, delay){
var time = null;
//防抖函数里传入业务逻辑fn()和延迟时间delay优化全局变量time,把变量time作为私有变量放入debounce函数中
return function(){
//此处是防抖逻辑
if(time != null){
clearTimeout(time)
}
time = setTimeout(() => {
fn.call(this) // 注:此处需改变this指针,否则业务逻辑中的this指向window
}, delay);
}
}
节流
定义: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时;控制执行次数,n秒内多次触发事件只有1次生效
简单版本:
// interval 间隔时间,也就是cd的长短
function throttle(fn, interval) {
//该变量用于记录上一次函数的执行事件
let lastTime = 0
const _throttle = function(...args) {
// 获取当前时间
const nowTime = new Date().getTime()
// cd剩余时间
const remainTime = nowTime - lastTime
// 如果剩余时间大于间隔时间,也就是说可以再次执行函数
if (remainTime - interval >= 0) {
fn.apply(this, args)
// 将上一次函数执行的时间设置为nowTime,这样下次才能重新进入cd
lastTime = nowTime
}
}
// 返回_throttle函数
return _throttle
}
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body{
height: 10000px;
}
</style>
</head>
<body>
</body>
<script>
window.onscroll = function(){
console.log('触发')
}
var flag = true;
var time = null;
window.onscroll = function(){
if(flag){ //标记变量为true时开始计时
time = setTimeout(() => {
console.log('触发')
flag = true;
}, 500);
}
flag = false; //n秒内flag值为false,不触发计时器
}
</script>
</html>
效果
以上方法将节流函数和业务逻辑放到一起,代码块不优雅,后期难以维护
可以将节流函数与业务逻辑单独封装,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body{
height: 10000px;
}
</style>
</head>
<body>
</body>
<script>
window.onscroll = throttle(task, 500)
//业务逻辑
function task(){
console.log('触发');
}
//节流函数
//封装一个名为throttle,且返回值为函数的节流函数,来替代window.onscroll后承接的内容。因为返回值为函数,所以用闭包思想来实现封装
//节流函数里传入业务逻辑fn()和延迟时间delay初始化标记变量flag,值为true,优化全局变量flag,time,把变量time作为私有变量放入throttle函数中
function throttle(fn,delay){
var flag = true;
var time = null;
// 组装业务逻辑函数和防抖函数
return function(){
if(flag){
time = setTimeout(() => {
fn.call(this)// 注:此处需改变this指针,否则业务逻辑中的this指向window
flag = true;
}, delay)
}
flag = false;
}
}
</script>
</html>