一
防抖:单位时间内,频繁触发事件,只执行最后一次
场景:搜索框搜索输入(利用定时器,每次触发先清掉以前的定时器,从新开始)
节流:单位时间内,频繁触发事件,只执行一次
场景:高频事件 快速点击,鼠标滑动、resize事件、scroll事件(利用定时器,等定时器执行完毕,才开启定时器,不用打断)
一般用lodash库,利用里面的debounce(防抖)和throttle(节流)来做
二
【前端面试题:防抖与节流(二)】 https://www.bilibili.com/video/BV1ig411u7LG/?share_source=copy_web&vd_source=c1fe9c75396fdc6f65b56d15f5eb00b3
防抖
防抖:用户触发事件过于频繁,只需要最后一次事件的操作
案例一:输入框触发过于频繁
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text">
<script>
let inp = document.querySelector("input");
inp.oninput = function(){
console.log(this.value);
}
</script>
</body>
</html>
案例二:用定时器解决触发频繁问题
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text">
<script>
// 防抖方法一 好烦啊,我也不知道是哪些部分不懂,明明拆开我全晓得,烦躁
let inp = document.querySelector("input");
// t代表的是用户触发的次数
let t = null;
inp.oninput = function(){
if(t !== null){
clearTimeout(t);
}
t = setTimeout(()=>{
console.log(this.value);
},500)
}
</script>
</body>
</html>
案例三:用debounce解决触发频繁问题
案例二的代码据说是一团垃圾,因为业务逻辑和什么混在一起了,所以下面用闭包封的更好些
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text">
<script>
// 防抖方法二 debounce
let inp = document.querySelector("input");
let t = null;
inp.oninput = debounce(function(){
console.log(this.value);
},500)
function debounce(fn,delay){
return function(){
if(t !== null){
clearTimeout(t);
}
t = setTimeout(()=>{
fn.call(this);
},delay)
}
}
</script>
</body>
</html>
节流
节流就是控制执行次数
案例一:执行次数过多
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body{
height:800px;
}
</style>
</head>
<body>
<script>
window.onscroll = function(){
console.log("123");
}
</script>
</body>
</html>
案例二:定时器节流
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body{
height:800px;
}
</style>
</head>
<body>
<script>
// 节流方法一 定时器
let flag = true;
window.onscroll = function(){
if(flag){
setTimeout(()=>{
console.log("111");
flag = true;
},500)
}
flag = false;
}
</script>
</body>
</html>
案例三:throttle节流
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
body{
height:800px;
}
</style>
</head>
<body>
<script>
// 节流方法二:throttle
let flag = true;
window.onscroll = throttle(function(){
console.log("111");
},500)
function throttle(fn,delay){
let flag = true;
return function(){
if(flag){
setTimeout(()=>{
fn.call(this)
flag = true
},delay)
}
flag = false;
}
}
</script>
</body>
</html>
三
【手写函数防抖和节流】 https://www.bilibili.com/video/BV1pQ4y1M71e/?p=3&share_source=copy_web&vd_source=c1fe9c75396fdc6f65b56d15f5eb00b3
防抖
事件响应函数在一段时间后才执行,如果在这段事件内再次调用,则重新计算执行时间;当预定的时间内没有再次调用,则执行它
防抖的应用场景:scroll事件滚动触发
搜索框输入查询
表单验证
按钮提交事件
浏览器窗口缩放,resize事件
案列:没有使用防抖时
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
let count = 0;
let container = document.querySelector("#container");
function dosomeThing(){
container.innerHTML = count++;
}
container.onmousemove = dosomeThing;
</script>
</body>
</html>
案例2:使用防抖,在一段时间内执行一次
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
// 自己封装一个防抖函数
function debounce(func,wait){
let timeout;
return function(){
clearTimeout(timeout);
timeout = setTimeout(func,wait);
}
}
let count = 0;
let container = document.querySelector("#container");
function dosomeThing(){
container.innerHTML = count++;
}
// 这里进行了封装函数的调用debounce
container.onmousemove = debounce(dosomeThing,1000);
</script>
</body>
</html>
案列3:使用防抖,同时解决this指向和event事件指向问题,再添加一个立即执行参数
这里的代码就不太懂了
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
// 自己封装一个防抖函数
function debounce(func,wait,immediate){
let timeout;
return function(){
// 改变this指向
let context = this;
// 解决e的指向问题
let args = arguments;
clearTimeout(timeout);
// 立即执行
if(immediate){
let callNow = !timeout;
timeout = setTimeout(()=>{
timeout = null
},wait);
if(callNow) func.apply(context,args)
}else{
timeout = setTimeout(function(){
func.apply(context);
},wait);
}
}
}
let count = 0;
let container = document.querySelector("#container");
// 事件event/e的指向
function dosomeThing(e){
// console.log(this);
// console.log(e);
container.innerHTML = count++;
}
// 这里进行了封装函数的调用debounce
container.onmousemove = debounce(dosomeThing,500,true);
</script>
</body>
</html>
案列4:增加一个取消防抖按钮,返回函数返回值
这个代码我就更不懂了
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<button id="btn">取消防抖</button>
<script>
// 自己封装一个防抖函数
function debounce(func,wait,immediate){
let timeout,result;
let decounced = function(){
// 改变this指向
let context = this;
// 解决e的指向问题
let args = arguments;
clearTimeout(timeout);
// 立即执行
if(immediate){
let callNow = !timeout;
timeout = setTimeout(()=>{
timeout = null
},wait);
if(callNow) result = func.apply(context,args)
}else{
timeout = setTimeout(function(){
func.apply(context);
},wait);
}
return result;
}
decounced.cancel = function(){
clearTimeout(timeout);
timeout = null;
}
return decounced;
}
let count = 0;
let container = document.querySelector("#container");
// 事件event/e的指向
function dosomeThing(e){
// console.log(this);
// console.log(e);
container.innerHTML = count++;
}
let doSome = debounce(dosomeThing,800);
btn.onclick = function(){
doSome.cancel()
}
// 这里进行了封装函数的调用debounce
container.onmousemove = doSome;
</script>
</body>
</html>
节流 throttle
如果你持续触发事件,每隔一段时间,只执行一次事件
这个笔记记得我自己都蒙了,没有一下记完,搞不懂当初的自己想要怎么记它。
应用场景:
DOM元素的拖拽
射击游戏
计算鼠标移动的距离
监听scroll滚动事件
案列一:借助别人写好的throttle函数
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js"></script>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
let count = 0;
// 演示事件是如何频繁发生的
let container = document.querySelector("#container");
let btn = document.querySelector("#btn");
function doSomeThing(e){
// event指向问题
console.log(e);
// 改变执行函数内部this的指向
console.log(this);
// 可能会做回调或者ajax请求
container.innerHTML = count++;
return '想要的结果'
}
container.onmousemove = _.throttle(doSomeThing,2000,{
// 第一次禁止执行,最后一次也是
leading:false,
trailing:false
});;
</script>
</body>
</html>
案列二:使用时间戳实现节流
鼠标移进去立即执行,隔两秒再执行,离开不执行
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function throttle(func,wait){
let context,args;
// 之前的时间戳
let old = 0;
// let previous = 0;
return function(){
context = this;
args = arguments;
// 获取当前的时间戳
let now = new Date().valueOf();
if(now-old>wait){
// 立即执行
func.apply(context,args);
old = now;
}
}
}
let count = 0;
// 演示事件是如何频繁发生的
let container = document.querySelector("#container");
let btn = document.querySelector("#btn");
function doSomeThing(e){
// event指向问题
console.log(e);
// 改变执行函数内部this的指向
console.log(this);
// 可能会做回调或者ajax请求
container.innerHTML = count++;
return '想要的结果'
}
container.onmousemove = throttle(doSomeThing,2000);
</script>
</body>
</html>
案列三:使用定时器实现节流
第一次不会触发,最后一次会触发
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function throttle(func,wait){
let context,args,timeout;
//
return function(){
context = this;
args = arguments;
if(!timeout){
setTimeout(()=>{
timeout = null;
func.apply(context,args);
},wait)
}
}
}
}
let count = 0;
// 演示事件是如何频繁发生的
let container = document.querySelector("#container");
let btn = document.querySelector("#btn");
function doSomeThing(e){
// event指向问题
console.log(e);
// 改变执行函数内部this的指向
console.log(this);
// 可能会做回调或者ajax请求
container.innerHTML = count++;
return '想要的结果'
}
container.onmousemove = throttle(doSomeThing,2000);
</script>
</body>
</html>
案列4:时间戳和定时器一起使用实现节流
第一次上来就执行,最后一次离开执行
下面两个代码效果一样,不过进行了一些顺序改动,希望你好好看看分析分析
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function throttle(func,wait){
let context,args,timeout;
// 时间戳
let old =0;
return function(){
context = this;
args = arguments;
let now = new Date().valueof();
if(now-old > wait){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
func.apply(context,args);
old = now;
}
if(!timeout){
timeout =setTimeout(()=>{
old = new Date().valueOf();
timeout = null;
func.apply(context,args);
},wait)
}
}
}
}
let count = 0;
// 演示事件是如何频繁发生的
let container = document.querySelector("#container");
let btn = document.querySelector("#btn");
function doSomeThing(e){
// event指向问题
console.log(e);
// 改变执行函数内部this的指向
console.log(this);
// 可能会做回调或者ajax请求
container.innerHTML = count++;
return '想要的结果'
}
container.onmousemove = throttle(doSomeThing,2000);
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function throttle(func,wait){
let context,args,timeout;
// 时间戳
let old =0;
// 改动代码进行了一个封装
let later = function(){
old = new Date().valueOf();
timeout = null;
func.apply(context,args);
}
return function(){
context = this;
args = arguments;
let now = new Date().valueOf();
if(now-old > wait){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
func.apply(context,args);
old = now;
}else if(!timeout){
timeout = setTimeout(later,wait)
}
}
}
let count = 0;
// 演示事件是如何频繁发生的
let container = document.querySelector("#container");
let btn = document.querySelector("#btn");
function doSomeThing(e){
// event指向问题
console.log(e);
// 改变执行函数内部this的指向
console.log(this);
// 可能会做回调或者ajax请求
container.innerHTML = count++;
return '想要的结果'
}
container.onmousemove = throttle(doSomeThing,2000);
</script>
</body>
</html>
代码优化,几种一个代码实现
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#container{
width:100%;
height:200px;
line-height: 200px;
text-align: center;
color:#fff;
background-color: #444;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function throttle(func,wait,options){
let context,args,timeout;
// 时间戳
let old =0;
if(!options) options = {};
let later = function(){
old = new Date().valueOf();
timeout = null;
func.apply(context,args);
}
return function(){
context = this;
args = arguments;
let now = new Date().valueOf();
if(options.leading === false && !old){
old = now;
}
if(now-old > wait){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
func.apply(context,args);
old = now;
}else if(!timeout && options.trailing !== false){
timeout = setTimeout(later,wait)
}
}
}
let count = 0;
// 演示事件是如何频繁发生的
let container = document.querySelector("#container");
let btn = document.querySelector("#btn");
function doSomeThing(e){
// event指向问题
console.log(e);
// 改变执行函数内部this的指向
console.log(this);
// 可能会做回调或者ajax请求
container.innerHTML = count++;
return '想要的结果'
}
// 要变成第几种情况,注意改下面的leading和trailing的值就行
// 现在是第二种代码情况怎么写
container.onmousemove = throttle(doSomeThing,2000,{
leading:false,
trailing:true
});
</script>
</body>
</html>