1. 引入问题
-
当div 元素绑定了 mousemove 事件每次鼠标移动都会触发方法
-
如果这时请求后台数据的话,每次鼠标移动都会请求一次后台
-
造成后台访问量巨大且无效
-
所以我们要用防抖和节流来解决这个问题
1.1 案例
参考如下代码所存在的问题
当鼠标移动时就会触发方法,如果方法是请求后台方法会造成大量的请求
<!DOCTYPE html>
<html lang**="en"** >
<head>
<meta charset**="UTF-8"** >
<title>**Title**</title>
<style>
#myid{
width: 100%;
height: 200px;
line-height: 200**px;
text-align: center;
color: #fff;
background-color: #444;
background-size: 30px;
}
</style>
</head>
<body>
<div id="myid" ></div>
<script>
let count = 0;
let container= document.querySelector("#myid");
function dosomething() {
// 可能会做ajax请求
containe*.innerHTML = count++
}
container.onmousemove = dosomething
</script>
</body>
</html>
复制代码
1.2 使用第三方库解决
使用underscore来解决这个问题
<script src="underscore/index.js" ></script>
<script>
let count = 0;
let container = document.querySelector("#myid");
function dosomething() {
// 可能会做ajax请求
container.innerHTML = count++
}
// 例用underscore.js实现防抖
container.onmousemove = _.debounce(dosomething,1000)
</script>
复制代码
这样会发现当鼠标停止移动后1秒才出发函数这样就能有效的解决我们的问题
2. 防抖
防抖 :
- 事件响应函数在一段时间后才执行
- 如果在这段时间内再次调用,则需重新计算执行事件
- 当预定的时间内没有再次调用该函数,则执行dosomething
2.1 简单实现防抖
- 防抖的原理就是让函数定时任务执行
- 每次调用这个函数都清空所有的定时任务
- 这样就能保证延时执行最后一次
// 防抖的函数
function debounce(func,wait) {
// 定时任务变量
let timeout;
return function () {
// 清空所有的定时任务
clearTimeout(timeout)
// 定义定时任务
timeout = setTimeout(func,wait)
}
}
复制代码
2.2 this指向问题
2.2.1 存在问题
使我我们封装的函数this指向window
function dosomething() {
// 可能会做ajax请求
container.innerHTML = count++
console.log(this); // 简单的防抖函数指向window,第三方库this指向
}
复制代码
而使用第三方库我们会发现this指向的是该DOM节点
2.2.2 解决问题
- 我们通过把this存为一个变量context
- 在调用定时器的时候让func.apply(context)从而改变this指向
// 防抖的函数
function debounce(func, wait) {
// 定时任务变量
let timeout;
return function () {
// 改变执行函数内部的this指向
let context = this;
// 清空所有的定时任务
clearTimeout(timeout)
// 定义定时任务
timeout = setTimeout(function () {
func.apply(context);
},wait)
}
}
复制代码
2.3 Event的指向问题
2.3.1 存在问题
使我我们封装的函数e指向undifinde
function dosomething(e) {
// 可能会做ajax请求
container.innerHTML = count++
console.log(e);
}
复制代码
而第三方库效果为
2.3.2 解决问题
- 通过arguments获取函数的所有参数并赋值给一个变量args
- 在定时器中通过func.apply(context,args)添加参数
// 防抖的函数
function debounce(func, wait) {
// 定时任务变量
let timeout;
return function () {
// 改变event
let args = arguments;
// 改变执行函数内部的this指向
let context = this;
// 清空所有的定时任务
clearTimeout(timeout)
// 定义定时任务
timeout = setTimeout(function () {
func.apply(context,args);
},wait)
}
}
复制代码
2.4 立即执行
第三方库还有第三个参数可以让函数立即执行
- 立即执行:比如鼠标移动后马上执行之后1秒内不会再执行,直到下个1秒再立即执行
- 稍后执行:鼠标移动后不动1秒后才执行
这里我们通过第三个参数immediate
- 通过immediate参数来写if…else语句
- 第一次调用timeout没有值
- timeout没有值
- callNow = !timeout 为true
- timeout在wait秒内都有值即在调用该函数不会再执行
- timeout在wait秒后设置为null,再调用防抖函数也会立即执行
- 第二次调用当前timeout有值
- timeout有值
- callNow = !timeout 为false
- 过wait秒后设置timeout = null
- 并且没有执行函数
- immediate为false时原本逻辑不变
function debounce(func, wait,immediate) {
var timeout,result;
let decounced = function () {
// 改变event
let args = arguments;
// 改变执行函数内部的this指向
let context = this;
// 清空所有的定时任务
if(timeout) clearTimeout(timeout);
// 如果立即执行
if(immediate){
// timeout为undifinde所以callNow为true
let callNow = !timeout;
timeout = setTimeout(()=>{
timeout = null;
},wait);
// 立即执行
if(callNow) result = func.apply(context,args);
}else{
// 不会立即执行
timeout = setTimeout(function () {
func.apply(context,args);
},wait)
}
// 返回这个立即执行的函数
return result;
}
return decounced;
}
复制代码
2.5 取消功能
当我们再1秒内(还没执行完成时)点击按钮可以点击取消执行
首先我们的主函数
let count = 0;
let container = document.querySelector("#myid");
function dosomething(e) {
// 可能会做ajax请求
container.innerHTML = count++
console.log(e);
}
// 防抖函数实例化
let dosome = debounce(dosomething,1000,false);
container.onmousemove = dosome
// 点击按钮取消方法
function quxiao() {
dosome.cancel();
}
复制代码
我们需要在debounce函数中实现cancel方法
- 使用clearTimeout直接清除定时器
- 把timeout设置为null(为了配合立即执行)
// 取消未完成事件的方法
decounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
}
复制代码
完整代码
function debounce(func, wait,immediate) {
var timeout,result;
let decounced = function () {
// 改变event
let args = arguments;
// 改变执行函数内部的this指向
let context = this;
// 清空所有的定时任务
if(timeout) clearTimeout(timeout);
// 如果立即执行
if(immediate){
// timeout为undifinde所以callNow为true
let callNow = !timeout;
timeout = setTimeout(()=>{
timeout = null;
},wait);
// 立即执行
if(callNow) result = func.apply(context,args);
}else{
// 不会立即执行
timeout = setTimeout(function () {
func.apply(context,args);
},wait)
}
// 返回这个立即执行的函数
return result;
}
// 取消未完成事件的方法
decounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
}
return decounced;
}
复制代码
3. 节流
- 如果持续触发事件,每隔一段时间,只执行一次
- 所以我们分两种情况来实现
- 第一次触发,最后不会被调用触发函数
- 第一次不会触发,最后一次会触发
let count = 0;
let container = document.querySelector("#myid");
let btn = document.querySelector("#btn");
function doSomeThing(e) {
container.innerHTML = count++
return "想要的结果"
}
container.onmousemove = _.throttle(doSomeThing,2000,{
leading:true, // 时间段开始时执行
trailing:false// 时间段结束时执行
});
复制代码
3.1 第一次触发
使用时间戳实现节流
- 利用闭包,里面记录一个old时间戳
- 每次执行时都把新的now时间戳赋值给old
- now-old > wait 则执行函数
// 第一次触发,最后不会调用触发函数
function throttle(func,wait){
let context,args;
//之前的时间戳
let old = 0;
return function(){
context = this;
args = arguments;
//获取当前的时间戳
let now = new Date().valueOf();
if(now-old > wait){
// 立即执行
func.apply(context,args);
old = now;
}
}
}
复制代码
3.2 最后一次触发
和防抖类似,都是使用延时执行
- 第一次timeout为空所以!timeout为true会立即执行
- 之后定时器直到wait时间后timeout才为null,才会执行
// 第一次不会触发,最后一次会触发
function throttle(func,wait){
let context,args,timeout ;
return function(){
context = this;
args = arguments;
if(!timeout){
timeout = setTimeout(()=>{
timeout = null;
func.apply(context,args);
},wait)
}
}
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END