文 / 景朝霞
来源公号 / 朝霞的光影笔记
ID / zhaoxiajingjing
目录:0 / 应用场景(1)防抖he节流de概念(2)小技巧:标识判断(3)小技巧:按钮置灰,移除事件1 / 函数防抖 debounce(1)搭架子(2)设置定时器标识(3)设置立即执行(4)完整的:函数防抖debounce2 / 函数节流 throttle(1)搭架子(2)超过wait再次触发(3)wait时间内的定时器只需要设定一个(4)刚好遇见临界点(5)完整的:函数节流 throttle
————————第二天————————
0 / 应用场景
函数的防抖(debounce)和节流(throttle)
在 高频 触发的场景下,需要进行防抖和节流,比如:
① 狂点一个按钮 给你的爱豆投票的按钮
② 页面滚动 一下加载500+的数据,往下滚动的时候
③ 输入模糊匹配 百度的搜索条,每次输入都在触发搜索的接口
④ ……
html
<head> <style> * { margin: 0; padding: 0; } html, body { height: 300%; background: -webkit-linear-gradient(top left, lightblue, orange, lightgreen); background: linear-gradient(top left, lightblue, orange, lightgreen); }style>head><body> <button id="submit">点我呀~~~button>body>
△ html
JS代码
submit.onclick = function (){ // 当我狂点按钮时,就会一直输出"hello" console.log('hello');};window.onscroll = function (){ // 默认情况下,页面滚动中: // 浏览器在最快的反映时间内(4~6ms) // 就会识别监听一次事件触发 // 把绑定的方法执行, // 这样导致方法执行的次数过多 // 造成不必要的资源浪费 console.log('world');};
△ JS
(1)防抖he节流de概念
我们自己可以规定:多长时间内,触发2+次就算是“高频”触发了,那封装的方法,就可以指定这个频率
防抖特点:在某一次高频触发下,只识别一次,可以控制是在开始的时候触发,还是在最后一次触发
假装:咱规定500ms内触发多次都算是高频触发,只要我们检测到是高频触发了,则在本次频繁操作下也只触发一次
哪怕咱疯狂连续点击了10min,也只识别一次,因为每次点击的间隔都是小于500ms的
节流特点:在某一次高频触发下,不是只识别一次,按照我们设定的时间间隔(自己设定的频率),每到达这个频率都会触发一次。
假装:咱规定的频率是500ms,一直操作了10min,那么触发的次数:(10*60*1000)/500
一直狂点10min,每隔500ms识别一次
(2)小技巧:标识判断
let flag = false;submit.onclick = function (){ if(flag) return; flag = true; console.log('hello'); setTimeout(()=>{ flag=false; }, 500);};
△ 在间隔多长时间后可以点
(3)小技巧:按钮置灰,移除事件
function handle(){ submit.onclick = null; submit.disabled = true; //...CODE console.log('hello'); setTimeout(()=>{ submit.onclick = handle; submit.disabled = false; },500);}submit.onclick = handle;
△ 按钮置灰,移除事件
1 / 函数防抖 debounce
func[function]
:最后要触发执行的函数
wait[number]
:"频繁"设定的界限
immediate[boolean]
:默认多次操作,咱识别的是最后一次,如果传参了immediate=true,让其识别第一次的
(1)搭架子
/** * @params * func[function]:最后要触发执行的函数 * wait[number]:"频繁"设定的界限 * immediate[boolean]:默认多次操作,我们识别的是最后一次,但是immediate=true,让其识别第一次的 */function debounce(func, wait, immediate){ if(typeof func !== 'function') throw new TypeError('func must be a function'); if(typeof wait === 'undefined') wait = 500; if(typeof wait === 'boolean') { immediate = wait; wait = 500; } if(typeof immediate !== 'boolean') immediate = false; //...CODE return function proxy(...args){ let self = this;//=> this被点击的按钮 func.call(self, ...args); };}function handle(event){ console.log('hello');}submit.onclick = debounce(handle, 500, true);// sumbit.onclick = proxy;// submit.onclick = debounce(handle, true);// submit.onclick = debounce(handle);
△ 对于三个参数的处理默认值
sumbit.onclick = proxy;
疯狂点击按钮时,proxy函数会疯狂执行
在proxy中,会根据频率管控handle执行的次数
浏览器在4~6ms就会监听一次submit的点击事件,这个是咱没法拦着的。但,咱自己可以重构代理函数:柯理化函数
柯理化函数:预先存起一些东西,以后当我点击按钮时,才会去执行
闭包的应用
(2)设置定时器标识
function debounce(func, wait, immediate){ // ...CODE:默认值处理 return function proxy(...args){ let self = this; // 如果这样写的话 // 在疯狂点击按钮时 // 每隔4~6ms会有一个定时器去排队 // 那么在间隔wait时间内,我点多少次 // 就会有每隔4~6ms,会有一个定时器去排队了 // 【此时,需要一个定时器的标识来控制不需要排队的清掉】 setTimeout(()=>{ func.call(self, ...args); }, wait); }}function handle(event){ console.log('hello');}submit.onclick = debounce(handle, 500, true);// submit.onclick = proxy;
△ 定时器
function debounce(func, wait, immediate){ // ...CODE:默认值处理 let timer = null; return function proxy(...args){ let self = this; clearTimeout(timer); timer = setTimeout(()=>{ func.call(self, ...args); }, wait); }}function handle(){ console.log('hello');}submit.onclick = debounce(handle, 500, true);// submit.onclick = proxy;
△ 设置定时器标识
假装:咱规定在500ms内,如果有第二次点击事件触发了,那么,就是高频的
第一次proxy执行,设定了一个定时器
过了4ms,第二次proxy执行,需要清除前面的定时器,重新设定一个定时器
……
在500ms内,疯狂点击100次了,那么前面的99次设定的定时器都被清掉了,只留了最后一次的定时器
(3)设置立即执行
function debounce(func, wait, immediate){ // ...CODE:默认值处理 let timer = null; return function proxy(...args){ let self = this, now = immediate && !timer; clearTimeout(timer); timer = setTimeout(()=>{ timer = null; !immediate ? func.call(self, ...args) : null; }, wait); //=> 第一次触发立即执行 now ? func.call(self, ...args) : null; }}function handle(){ console.log('hello');}submit.onclick = debounce(handle, 500, true);// submit.onclick = proxy;
△ 设置immediate的参数了
immediate=true
设置了立即执行,那么定时器里面的就不用执行了
(4)完整的:函数防抖debounce
function debounce(func, wait, immediate){ if(typeof func !== 'function') throw new TypeError('func must be a function'); if(typeof wait === 'undefined') wait = 500; if(typeof wait === 'boolean') { immediate = wait; wait = 500; } if(typeof immediate !== 'boolean') immediate = false; let timer = null; return function proxy(...args){ let self = this, now = immediate && !timer; clearTimeout(timer); timer = setTimeout(()=>{ timer = null; !immediate ? func.call(self, ...args) : null; }, wait); now ? func.call(self, ...args) : null; }}
△ 函数防抖debounce
2 / 函数节流 throttle
(1)搭架子
/** * 函数防抖 * @param {function} func * @param {number} wait * @returns */function throttle(func, wait){ if(typeof func !== 'function') throw new TypeError('func must be a function'); if(typeof wait === 'undefined') wait = 500; return function proxy(...args){ let self = this; };}function handle(){ console.log('world');}window.onscroll = throttle(handle, 500);// window.onscroll = proxy;
△ 搭架子
(2)超过wait再次触发
△ 图1_操作
function throttle(func, wait){ // ...CODE 默认值容错 let timer = null, previous = 0; // 记录上一次操作的时间 return function proxy(...args){ let self = this, now = new Date(), // 当前这次触发的操作的时间 remaining = wait - (now - previous); // 两次的间隔时间 if(remaining <= 0) { // 两次间隔时间超过wait了,直接执行即可 previous = now; // 下一次的开始时间 func.call(self, ...args); } else { // 两次触发间隔时间没有超过wait,则设置定时器,让其等待remaining这么久之后执行一次 timer = setTimeout(function (){ previous = new Date(); // 下一次开始的时间 func.call(self, ...args); }, remaining); } };}function handle(){ console.log('world');}window.onscroll = throttle(handle, 500);// window.onscroll = proxy;
△ 第一次会立即执行一次
new Date() - 0
的值肯定是超过500ms的,第一次会立即执行一次
(3)wait时间内的定时器只需要设定一个
timer = setTimeout(....);
△ timer
① 给timer赋值,timer是一个数字,存储当前是第几个定时器
② clearTimeout(timer) 从系统中清除定时器,系统中没有定时器,但是timer的值还是那个数字
③ timer = null
重新赋值,可以基于timer是否为null了解到是否还有定时器
比如:小虾同学去银行要开卡,拿到一个排号101
....到柜台上,把业务办好了,需要在系统中删除这个号码
但是,纸条还在你手里
然后,柜台业务员会把你手里的纸片儿拿过去,撕掉
timer=null 就是把这个定时器撕掉了
△ 图2
function throttle(func, wait){ if(typeof func !== 'function') throw new TypeError('func must be a function'); if(typeof wait === 'undefined') wait = 500; let timer = null, previous = 0; // 记录上一次操作的时间 return function proxy(...args){ let self = this, now = new Date(), // 当前这次触发的操作的时间 remaining = wait - (now - previous); // 两次的间隔时间 if(remaining <= 0) { // 两次间隔时间超过wait了,直接执行即可 previous = now; // 下一次的开始时间 func.call(self, ...args); } else if(!timer){ // 两次触发间隔时间没有超过wait,则设置定时器,让其等待remaining这么久之后执行一次 // 【前提】没有设置过定时器 timer = setTimeout(function (){ clearTimeout(timer); timer = null; previous = new Date(); // 下一次开始的时间 func.call(self, ...args); }, remaining); } };}
△ 清掉定时器
△ 图3_1.清掉多余的定时器 2. 临界点,清掉定时器
(4)刚好遇见临界点
function throttle(func, wait){ if(typeof func !== 'function') throw new TypeError('func must be a function'); if(typeof wait === 'undefined') wait = 500; let timer = null, previous = 0; // 记录上一次操作的时间 return function proxy(...args){ let self = this, now = new Date(), // 当前这次触发的操作的时间 remaining = wait - (now - previous); // 两次的间隔时间 if(remaining <= 0) { // 两次间隔时间超过wait了,直接执行即可 clearTimeout(timer); timer = null; previous = now; // 下一次的开始时间 func.call(self, ...args); } else if(!timer){ // 两次触发间隔时间没有超过wait,则设置定时器,让其等待remaining这么久之后执行一次 // 【前提】没有设置过定时器 timer = setTimeout(function (){ clearTimeout(timer); timer = null; previous = new Date(); // 下一次开始的时间 func.call(self, ...args); }, remaining); } };}
△ 1.清掉多余的定时器 2. 临界点,清掉定时器
(5)完整的:函数节流 throttle
function throttle(func, wait){ if(typeof func !== 'function') throw new TypeError('func must be a function'); if(typeof wait === 'undefined') wait = 500; let timer = null, previous = 0; return function proxy(...args){ let self = this, now = new Date(), remaining = wait - (now - previous); if(remaining <= 0) { clearTimeout(timer); timer = null; previous = now; func.call(self, ...args); } else if(!timer){ timer = setTimeout(function (){ clearTimeout(timer); timer = null; previous = new Date(); func.call(self, ...args); }, remaining); } };}function handle(){ console.log('world');}window.onscroll = throttle(handle, 500);// window.onscroll = proxy;
△ 函数节流
- end -
lodash的debounce:https://github.com/lodash/lodash/blob/master/debounce.js
loadash的throttle:https://github.com/lodash/lodash/blob/master/throttle.js
从"你"到"更好的你",有无限可能~