保持对代码的热爱,并保存怀疑态度
在说防抖和节流之前我们先说一下闭包
什么是闭包?后面会利用到闭包 点击查看闭包原理及使用
通过作用域的嵌套,触发计算机的垃圾回收机制(硬盘),将原本的局部变量进化成私有变量的环境,叫闭包。
function fn(){
var a = 10;
return function(){
a ++ ;
console.log(a);
}
}
var f = fn();
f();
防抖和节流的概念
日常开发过程中,滚动事件做复杂计算频繁调用回调函数很可能会造成页面的卡顿,这时候我们更希望把多次计算合并成一次,只操作一个精确点,JS把这种方式称为debounce(防抖)和throttle(节流)
防抖例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div{
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
<div>
我撑撑撑
</div>
</body>
<script>
function Fn(){
var sTOP= document.body.scrollTop || document.documentElement.scrollTop;
console.log(sTOP);
}
onscroll= Fn //滚动时执行函数获取到滚动了多少
</script>
</html>
上诉代码中当你只要滚动就会触发滚动事件,而且触发的频率会很高。可以试一试按上下键,就是按一下也会触发很多次这个函数,浪费性能,所以这时候防抖就出现了。
函数防抖
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。
利用了计时器和闭包
function debounce(fn, wait) {
var timeout = null; //定义一个定时器 这里利用了闭包!
return function() {
if(timeout !== null)
clearTimeout(timeout); //清除这个定时器
timeout = setTimeout(fn, wait);
}
}
//处理函数
function Top(){
var sTOP= document.body.scrollTop || document.documentElement.scrollTop;
console.log(sTOP);
}
// 滚动事件
window.addEventListener('scroll', debounce(Top, 1000));
这时候你再重复的触发滚动事件就不会像未做防抖之前一直触发获得滚动值,所以综上所述,当持续触发scroll函数,handle函数只会在1秒时间内执行一次,在滚动过程中并没有持续执行,有效减少了性能的损耗
节流
当持续触发事件时,保证在一定时间内只调用一次事件处理函数,意思就是说,假设一个用户一直触发这个函数,且每次触发小于既定值,函数节流会每隔这个时间调用一次。
实现函数节流我们主要有两种方法:时间戳和定时器
节流与防抖的区别
防抖是将多次执行变为最后一次执行,节流是将多次执行变为每隔一段时间执行
防抖和节流能有效减少浏览器引擎的损耗,防止出现页面堵塞卡顿现象,应该熟练掌握。
时间戳实现节流
var throttle = function(func, delay) {
var prev = Date.now(); //闭包,获取到现在的日期
return function() {
var context = this; //this指向window
var args = arguments;
var now = Date.now();
if (now - prev >= delay) { //现在的日期减去刚刚的日期是不是大于传参的时间
func.apply(context, args); //改变this指向为传入的函数,覆盖原函数的arguments
prev = Date.now();
}
}
}
//处理函数
function Top(){
var sTOP= document.body.scrollTop || document.documentElement.scrollTop;
console.log(sTOP);
}
// 滚动事件
window.addEventListener('scroll', throttle(Top, 1000));
定时器实现节流
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
}
}
//处理函数
function Top(){
var sTOP= document.body.scrollTop || document.documentElement.scrollTop;
console.log(sTOP);
}
// 滚动事件
window.addEventListener('scroll', throttle(Top, 1000));
效果图: 当一直滚动的时候只会每1ms后执行获取到值
当触发事件的时候,我们设置了一个定时器,在没到1000ms之前这个定时器为null,而到了规定时间执行这个函数并再次把定时器清除。也就是说当第一次触发事件,到达规定时间再执行这个函数,执行之后马上清除定时器,开始新的循环,那么我们看到的效果就是,滚动之后没有马上打印,而是等待1000ms打印,有一个延迟的效果,并且这段时间滚动事件不会执行函数。
单用时间戳或者定时器都有缺陷,我们更希望第一次触发马上执行函数,最后一次触发也可以执行一次事件处理函数
var throttle = function(func, delay) {
var timer = null;
var startTime = Date.now(); //设置开始时间
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime); //剩余时间
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) { // 第一次触发立即执行
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining); //取消当前计数器并计算新的remaining
}
}
}
//处理函数
function Top(){
var sTOP= document.body.scrollTop || document.documentElement.scrollTop;
console.log(sTOP);
}
// 滚动事件
window.addEventListener('scroll', throttle(Top, 1000));