在讨论防抖和节流之前咱们先看一个需求,需求是这样的,监听页面的scroll事件,当页面拖动到最底端时,加载更多。实现代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
*{padding:0;margin:0}
.content{
height:3000px;
}
</style>
</head>
<body>
<div class="content">
页面
</div>
<script>
document.onscroll = function(){
console.log("执行频率测试")
// 页面总高度:
var pageHeight = document.body.scrollHeight;
// 显示器高度;
var screenHeight = document.documentElement.clientHeight;
// 页面被滚动条卷入的高度
var scrollHeight = document.documentElement.scrollTop
if(scrollHeight+screenHeight>=pageHeight){
console.log("加载更多");
}
}
</script>
</body>
</html>
但是这个代码有个问题,那就是执行的频率太高了,只要拖动滚动条,就会执行事件回调,并且会实时计算页面高度,页面滚动高度,屏幕的高度,然后进行计算,这些计算是非常耗费js性能的,我们要怎么优化呢?
我们可以这样来优化,分为如下几个步骤:
1、我们把判断是否加载更多的逻辑放在一个延时器里面。
2、当触发scroll事件时,我们做一个判断,判断有无延时器。
3、如果没有延时器,我们开启延时器,延时600毫秒判断是否加载更多。4、如果已经有延时器了,我们清除延时器,重新开启延时器。
这样就能限制scoll持续触发事件毁掉了。
scroll内部函数的运行流程图如下:
按照这张图,实现代码如下:
<script>
// 抽离计算页面高度,加载更多逻辑
function more(){
console.log("执行频率测试")
// 页面总高度:
var pageHeight = document.body.scrollHeight;
// 显示器高度;
var screenHeight = document.documentElement.clientHeight;
// 页面被滚动条卷入的高度
var scrollHeight = document.documentElement.scrollTop
if(scrollHeight+screenHeight>=pageHeight){
console.log("加载更多");
}
}
// 延时器全局变量
var timeout= null;
document.onscroll = function(){
// 判断是否启动了延时器;
if(!timeout){
// 未启动,开启延时器
timeout = setTimeout(function(){
timeout =null;
more()
},600)
}else{
// 已经启动,清除延时器,重新计时
clearTimeout(timeout);
timeout = setTimeout(function(){
more()
},600)
}
}
</script>
以上便是防抖的使用了,我们可以根据上面实现的过程,给防抖下一个定义:当某个事件持续触发时,我们可以开启一个延时器,当事件触发的间隔小于延时器设置的时间时,便将其延后,直到事件触发间隔大于延时器设置的时间时才真正触发事件处理逻辑。如果事件的触发间隔时间一直小于延时器时间,真正处理函数一直不会执行。
我们再次观察上面的代码,发现为了实现防抖,我们增加了两个全局变量一个函数more一个timeout,这不是我们想看到的,并且代码不通用,比方我们还要实现其他防抖功能,上面的代码又的重新写一遍,我们这针对这几个缺点,优化如下:
首先第一步优化,我们将scroll事件函数用一个立即执行函数进行包裹,形成独立作用域,如下:
<script>
// 抽离计算页面高度,加载更多逻辑
function more(){
console.log("执行频率测试")
// 页面总高度:
var pageHeight = document.body.scrollHeight;
// 显示器高度;
var screenHeight = document.documentElement.clientHeight;
// 页面被滚动条卷入的高度
var scrollHeight = document.documentElement.scrollTop
if(scrollHeight+screenHeight>=pageHeight){
console.log("加载更多");
}
}
document.onscroll = (function debounce(more){
var timeout= null;
return function(){
// 判断是否启动了延时器;
if(!timeout){
// 未启动,开启延时器
timeout = setTimeout(function(){
timout = null;
more()
},600)
}else{
// 已经启动,清除延时器,重新计时
clearTimeout(timeout);
timeout = setTimeout(function(){
more()
},600)
}
}
})(more)
</script>
这样的话延时器变量就被包裹起来了,接着优化,将debounce函数抽离出来,优化如下:
<script>
// 抽离计算页面高度,加载更多逻辑
function more(){
console.log("执行频率测试")
// 页面总高度:
var pageHeight = document.body.scrollHeight;
// 显示器高度;
var screenHeight = document.documentElement.clientHeight;
// 页面被滚动条卷入的高度
var scrollHeight = document.documentElement.scrollTop
if(scrollHeight+screenHeight>=pageHeight){
console.log("加载更多");
}
}
function debounce(more){
var timeout= null;
return function(){
// 判断是否启动了延时器;
if(!timeout){
// 未启动,开启延时器
timeout = setTimeout(function(){
timout = null;
more()
},600)
}else{
// 已经启动,清除延时器,重新计时
clearTimeout(timeout);
timeout = setTimeout(function(){
more()
},600)
}
}
}
document.onscroll = debounce(more)
</script>
这样我们就完成了一个高阶函数debounce,函数的参数为函数,还有一点需要优化,有得时候我们在调用事件函数的时候,需要访问调用onscroll的dom对象和事件对象,这就需要我们做一些处理了,怎么处理了?看下面代码:
<script>
// 抽离计算页面高度,加载更多逻辑
function more(t){
console.log(t)
console.log(this)
console.log("执行频率测试")
// 页面总高度:
var pageHeight = document.body.scrollHeight;
// 显示器高度;
var screenHeight = document.documentElement.clientHeight;
// 页面被滚动条卷入的高度
var scrollHeight = document.documentElement.scrollTop
if(scrollHeight+screenHeight>=pageHeight){
console.log("加载更多");
}
}
function debounce(more){
var timeout= null;
return function(){
// 获取事件对象中
let event = arguments[0];
// 获取this指向
var context = this;
// 判断是否启动了延时器;
if(!timeout){
// 未启动,开启延时器
timeout = setTimeout(function(){
timemout = null;
// 绑定this指向,并传递事件对象
more.call(context,event)
},600)
}else{
// 已经启动,清除延时器,重新计时
clearTimeout(timeout);
timeout = setTimeout(function(){
// 绑定this指向,并传递事件对象
more.call(content,event)
},600)
}
}
}
document.onscroll = debounce(more)
</script>
我们通过观察代码发现,debounce执行返回的函数,最终会以DOM事件的方式调用,所以debounce返回的这个函数的this指向调用者,我们将其保存为context,而函数的默认参数为事件对象,我们将其保存为event,然后调用more函数的时候,用call绑定context,并将event作为第一个参数传入。