防抖
原由:
为了防止用户频繁触发某一事件,导致的卡顿,堵塞,崩溃的现象,优化性能和体验
应用场景:
- scroll事件滚动触发
- 框输入查询
- 表单验证
- 按钮提交事件
- 浏览器窗口缩放,resize事件
防抖原理:
事件响应函数(dosomeThing)在一段时间后(时长自己定义:如300ms)才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定的时间内没有再次调用该函数,则执行该函数(dosomeThing)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 引入别人封装好的js文件(underscore.js)CDN引入: https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js <div class="container" style="width: 100%;height: 300px;background-color: skyblue;text-align: center;font-size: 50px;"></div> <script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js"></script> <script > let container = document.querySelector(".container"); let count = 0 ; function dosomeThing(e){ <!-- event指向问题 --> console.log(e); <!-- 改变执行函数内部this的指向 --> console.log(this); <!-- 可能会做回调或者ajax请求 --> container.innerHTML = count++; return "结果" } // 高阶函数 防抖 调用防抖函数 _.debounce() container.onmousemove = _.debounce(dosomeThing,300); //参数,第一:处理事件函数,第二:触发时间,第三:默认false(true为立即执行函数) // _.debounce(dosomeThing,300); // container.onmousemove = dosomeThing; // 不做处理 </script>
|
第二种方法:手写简版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script type="text/javascript"> function fengZn(fn,wait){ var timer; return function(){ <!-- 1.清除定时器; --> clearTimeout(timer); <!-- 2.重新注册定时器; --> timer = setTimeout(()=>{ console.log(fn); fn() },wait) } } function resizeEvent(){ console.log("他扒拉我") } window.addEventListener("resize",fengZn(resizeEvent,2000)); </script>
|
第三种方法:模拟库,手写封装一个健壮版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <div class="container" style="width: 100%;height: 300px;background-color: skyblue;text-align: center;font-size: 50px;"></div> <button class="btn">取消防抖</button> <script> let container = document.querySelector(".container"); let count = 0 ; // 触发后,处理事件。 function dosomeThing(){ container.innerHTML = count++; } <!-- 传入参数处理事件函数,触发时间,是否立即执行 --> function debounce(func,wait,immediate){ var timeout,result; let decounced = function(){ <!-- 重置this,args指向 --> var context = this; var args = arguments; if(timeout) clearTimeout(timeout); if(immediate){ var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; },wait); <!-- 立即执行 --> if(callNow) result = func.apply(context,args); }else{ <!-- 不会立即执行 --> timeout = setTimeout(function(){ func.apply(context,args); },wait); } return result; } <!-- 取消防抖,定义一个取消方法cancel --> decounced.cancel = function(){ clearTimeout(timeout) <!-- 防止内存泄露 --> timeout = null; } return decounced; } let btn = document.querySelector(".btn"); let dosome = debounce(dosomeThing,1000); container.onmousemove = dosome; // 取消防抖 btn.onclick = function(){ dosome.cancel() } </script>
|
节流
原由:
防止多次调用函数,减少不必要的性能损耗
应用场景:
- DOM元素的拖拽功能实现
- 射击游戏
- 计算鼠标移动的距离
- 监听scroll滚动事件
- 点击事件
节流原理:
如果你持续触发事件,每隔一段时间,只执行一次事件
第一种方法:引入js库和 防抖 一样 CDN 引入 underscore.js文件,在调用函数方法时:
注意: 是调用方法: _.throttle()
_.throttle() ,传参的第三参数(可不传)是个对象,有两个变量可设置
leading: false, 设置是否禁止第一次触发执行
trailing: false 设置是否禁止最后一次触发执行
1 2
| // 高阶函数 防抖 调用节流函数方法 _.throttle() container.onmousemove = _.throttle(dosomeThing,300);
|
第二种方法:利用定时器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <script type="text/javascript"> function throttle(fn){ var canRun = true; return function(){ <!-- 如果开关关闭,则不允许执行后续语句 --> if(!canRun) return; fn() canRun = false; <!-- 在一定延迟之后,将开关变成true; --> setTimeout(()=>{ canRun = true; },1000) } } <!-- resize 回调事件/事件处理程序 --> function conName(){ console.log("他扒拉我") } window.addEventListener("resize",throttle(conName)); <!-- scroll 回调事件/事件处理程序 --> function scrollEvent(){ <!-- 滚动时要做的事情 --> console.log("滚动") } window.addEventListener("scroll",throttle(scrollEvent)); </script>
|
触发后调用相关函数方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| //利用underscore.js库 // let dosome = _throttle(dosomeThing,1000,{ // leading: false, // trailing: true //禁止最一次触发执行 // });
// let dosome = throttle(dosomeThing,2000);
let dosome = throttle(dosomeThing,1000,{ leading: false, trailing: true //禁止最一次触发执行 }); container.onmousemove = dosome;
|
1. 利用时间戳
第一次会触发,最后一次会触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function throttle(func,wait){ let context,args; // 之前的时间戳 let oldTime = 0; return function(){ context = this; args = arguments; // 获取当前的时间戳 let nowTime = new Date().valueOf(); if(nowTime-oldTime > wait){ // 立即执行 func.apply(context,args); oldTime = nowTime; } } }
|
2. 利用定时器
第一次不会触发,最后一次会触发
1 2 3 4 5 6 7 8 9 10 11 12 13
| function throttle(func,wait){ let context,args,timeout; return function(){ context = this; args = arguments; if(!timeout){ timeout = setTimeout(()=>{ func.apply(context,args); timeout = null; },wait) } } }
|
3. 结合时间戳和定时器使用
第一次直接执行,最后一次还会执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function throttle(func,wait){ let context,args,timeout; // 之前的时间戳 let oldTime = 0; let later = function(){ oldTime = new Date().valueOf(); timeout = null; func.apply(context,args); } return function(){ context = this; args = arguments; // 获取当前的时间戳 let nowTime = new Date().valueOf(); if(nowTime-oldTime > wait){ if(timeout){ clearTimeout(timeout); timeout = null; } // 立即执行 func.apply(context,args); oldTime = nowTime; }else if(!timeout){ timeout = setTimeout(later,wait) } } }
|
4. 节流优化
按需求控制第一次和最后一次触发的是否执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| function throttle(func,wait,options){ let context,args,timeout; // 之前的时间戳 let oldTime = 0; if (!options) options = {}; let later = function(){ oldTime = new Date().valueOf(); timeout = null; func.apply(context,args); } return function(){ context = this; args = arguments; // 获取当前的时间戳 let nowTime = new Date().valueOf(); if(options.leading === false && !oldTime){ oldTime = nowTime } if(nowTime - oldTime > wait){ // 第一次会直接执行 if(timeout){ clearTimeout(timeout); timeout = null; } func.apply(context,args); oldTime = nowTime; }else if(!timeout && options.trailing !== false){ // 最后一次也会被执行 timeout = setTimeout(later,wait) } } }
|