ES5进阶-防抖与节流
阅读 (659)一、浅谈防抖与节流
场景
因频繁执行DOM操作,资源加载等行为,导致UI停顿甚至浏览器崩溃
- window对象频繁的onresize、onscroll等事件
- 拖拽的mousemove事件
- 射击游戏的mousedown,keydown事件
- 文字输入,内容改变的keyup事件
场景复现
比如每次onscroll就会触发一次函数,又比如每次搜索一下就会向服务器发送一个请求,这样既没有意义,也很浪费资源
-
监听浏览器滚动事件,返回当前滚条与顶部的距离
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body style="background-color: red;height: 5000px;"> <script type="text/javascript"> function fn(){ var scrollTop = document.body.scrollTop || document.documentElement.scrollTop console.log("滚动条位置:" + scrollTop) } window.addEventListener("scroll", fn, false) </script> </body> </html>
问题:执行频率太高了,消耗资源,影响用户体验
-
实时搜索
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <label>letters:</label> <input type="text" name="inp" id="inp"/><br> <label>toUpperCase:</label> <input type="text" name="out" id="out"/> <script type="text/javascript"> var inp = document.getElementById("inp") var out = document.getElementById("out") function fn(event){ event = event || window.event out.value = event.target.value console.log("-------", event.target.value) } inp.addEventListener("keydup", fn, false) </script> </body> </html>
问题:如果每输入一个字符就发起请求的话也是比较消耗资源,并且搜索的内容不是想要的
防抖思想
A和B说话,A一直bbbbbb,当A持续说了一段时间的话后停止讲话,过了10秒之后,我们判定A讲完了,B开始回答A的话;如果10秒内A又继续讲话,那么我们判定A没讲完,B不响应,等A再次停止后,我们再次计算停止的时间,如果超过10秒B响应,如果没有则B不响应
节流思想
比如mouseover,resize,scroll这种事件,每当有变化的时候,就会触发一次函数,这样很浪费资源。就比如一个持续流水的水龙头,水龙头开到最大的时候很浪费水资源,将水龙头开得小一点,让他每隔200毫秒流出一滴水,这样能源源不断的流出水而又不浪费。而节流就是每隔n的时间调用一次函数,而不是一触发事件就调用一次,这样就会减少资源浪费
防抖与节流的区别
防抖与节流的前提都是某个行为持续地触发,不同之处只要判断是要优化到减少它的执行次数还是只执行一次就行
-
防抖例子
像仿百度搜索,就应该用防抖,当我连续不断输入时,不会发送请求;当我一段时间内不输入了,才会发送一次请求;如果小于这段时间继续输入的话,时间会重新计算,也不会发送请求
-
节流例子
像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多
二、防抖
防抖分为立即防抖
和非立即防抖
,最常见的例子就是搜索
非立即防抖:触发事件后函数不会立即执行,而是在n秒之后执行,如果n秒之内又触发了事件,则会重新计算函数执行时间
立即防抖:触发事件后函数会立即执行,然后n秒内不触发事件才会执行函数的效果
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<label>letters:</label>
<input type="text" name="inp" id="inp"/><br>
<label>toUpperCase:</label>
<input type="text" name="out" id="out"/>
<script type="text/javascript">
var inp = document.getElementById("inp")
var out = document.getElementById("out")
function fn(event){
event = event || window.event
out.value = event.target.value
console.log("-------", event.target.value)
}
inp.addEventListener("keydup", fn, false)
</script>
</body>
</html>
-
非立即防抖
function debounce(fn, wait) { var timer = null; var context = null; var args = null; var later = function() { fn.apply(context, args); timer = null; } return function() { content = this; args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(later, wait); } }
-
立即防抖
function debounce(fn, wait) { var timer = null; var context = null; var args = null; var later = function() { fn.apply(context, args); } return function() { content = this; args = arguments; if (timer) { clearTimeout(timer); } var callNow = !timer; timer = setTimeout(function() { timer = null; }, wait); if (callNow) { later(); } } }
-
非立即执行版和立即执行版的防抖函数结合,实现最终的双剑合璧版的防抖函数
/** * @desc 函数防抖 * @param func (function) 函数 * @param wait (number) 延迟执行毫秒数 * @param immediate (boolean) true 表立即执行,false 表非立即执行 */ function debounce(fn, wait, immediate = false) { var timer = null; var context = null; var args = null; var later = function() { fn.apply(context, args); if (!immediate) { timer = null; } } return function() { context = this; args = arguments; if (timer) { clearTimeout(timer); } if (immediate) { var callNow = !timer; timer = setTimeout(function() { timer = null; }, wait); if (callNow) { later(); } } else { timer = setTimeout(later, wait); } } }
三、节流
节流分为时间戳
和定时版本
,时间戳版和定时器版的节流函数的区别就是,时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候
高频事件:onscroll oninput resize onkeyup onkeydown onkerpress
节流单纯的降低代码执行的频率,保证一段时间内核心代码只执行一次
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body style="background-color: red;height: 5000px;">
<script type="text/javascript">
function fn(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
console.log("滚动条位置:" + scrollTop)
}
window.addEventListener("scroll", fn, false)
</script>
</body>
</html>
- 时间戳版本
function throttle(fn, wait) {
var context = null;
var args = null;
// 记录上次触发时间
var previous = 0;
return function() {
context = this;
args = arguments;
var now = Date.now()
// 本次事件触发与上一次的时间比较
// 如果隔间时间超过设定时间,即再次设置事件触发的定时器
if (now - previous >= wait) {
fn.apply(context, args);
// 更新最近事件触发的时间
previous = now;
}
}
}
-
定时器版本
function throttle(func, wait) { var context = null; var args = null; var timer = null; return function() { context = this; args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, wait); } } }
-
时间戳版和定时器版的节流函数结合起来,实现双剑合璧版的节流函数
function throttle(func, wait, type = 1) { var context = null; var args = null; if (type === 1) { var previous = 0; } else { var timer = null; } return function() { context = this; args = arguments; if (type === 1) { var now = Date.now() if (now - previous >= wait) { fn.apply(context, args); previous = now; } } else { if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, wait); } } } }