【进阶 6-3 期】深入浅出面试必考题 - 节流函数 throttle - 其中有underscore.js
源码解析
【进阶 6-4 期】深入浅出防抖函数 debounce- 其中有underscore.js
源码解析
节流(throttle)
函数节流:Throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds".
理解:函数节流指的是某个函数在一定时间间隔内(例如 3 秒)只执行一次,在这 3 秒内 无视后来产生的函数调用请求,也不会延长时间间隔。
原理和实现
函数节流触发事件:window对象的resize、scroll事件、拖拽时的mousemove事件、文字输入、自动完成的keyup事件。
实现方案:
-
第一种是通过时间戳来判断是否已到达执行时间,记录上次的执行时间戳,然后每次调用这个方法时,判断该次触发时间和上次执行的时间差,是否在规定的执行时间之外,如果是则执行,并更新上次执行的时间戳,如果不是,不执行函数,等待下一次触发,如此循环
-
第二种方法设置定时器,比如当scroll事件触发时,打印一个
holle world
,然后设置一个1000ms
定时器,此后每次触发scroll
事件触发回调,如果已经存在定时器,则回调不执行方法,直到定时器触发,handler
被清除,然后重新设置定时器。
第一种实现:
/** * 节流函数 * @param {function} fn - 回调函数 * @param {number} wait - 定时节流 */ const throttle = function(fn, wait = 500) { // 第一次执行设置时间为 0 let provious = 0 return function(...args) { let now = +new Date() // 将当前时间 和 上一次执行的时间对比 // 判断是否在节流周期中 if(now - provious > wait) { // 使用闭包,储存该次触犯时的时间戳 provious = now // 执行回调 fn.apply(this, args) } } } // 模拟回调函数 const func = function(...args) { // your code ... console.log('func方法', ...args) } // 调用节流 const betterFn = throttle(func, 1000) // 模拟事件触发,并传入参数 setInterval(betterFn, 2000, [1, 2, 3, 4])
第二种实现:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> * {margin: 0;padding: 0;} .father {width: 300px;height: 300px;background-color: #3de;margin: 100px auto;padding: 10px;overflow: auto;border: 10px solid red;} .son {width: 400px;height: 600px;background-color: yellowgreen;} </style> </head> <body> <div class="father"><div class="son"></div> </div> <script src="./节流.js"></script> </body> </html>
节流.js:
/** * 节流函数 * @param {function} fn - 回调函数 * @param {number} wait - 定时节流 */ const throttle = function(fn, wait = 500) { let timer = null return function(...args) { if(!timer) { // 执行回调 fn.apply(this, args) timer = setTimeout(() => { timer = null }, wait) } } } // 模拟回调函数 const func = function(...args) { // your code ... console.log('holle world', ...args) } // 调用节流 const betterFn = throttle(func, 1000) // 模拟scroll事件触发,并传入参数 let fNode = document.querySelector('.father'); fNode.onscroll = function(){ // 获取元素中被卷去的内容的距离 获取元素内部总被卷去的内容的横向距离 和 纵向距离 console.log('x:' + fNode.scrollLeft) console.log('y:' + fNode.scrollTop) // 在 1000ms内 触发不会执行回调 betterFn([1, 2, 3, 4]) }
防抖(debounce)
函数防抖:Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called".
防抖函数:指的是某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次。
// html样式同节流 /** * 防抖函数 * @param {function} fn - 回调函数 * @param {number} wait - 时间间隔 */ const debounce = function(fn, wait = 500) { let timer = null return function(...args) { if(timer) clearTimeout(timer) timer = setTimeout(() => { fn.apply(this, args) }, wait) } } // 模拟回调函数 const func = function(...args) { // your code ... console.log('fn 防抖执行了', ...args) } // 调用防抖 const betterFn = debounce(func, 1000) // 模拟scroll事件触发,并传入参数 let fNode = document.querySelector('.father'); fNode.onscroll = function(){ // 获取元素中被卷去的内容的距离 获取元素内部总被卷去的内容的横向距离 和 纵向距离 console.log('x:' + fNode.scrollLeft); console.log('y:' + fNode.scrollTop); betterFn([1, 2, 3, 4]) }
<input type="text" id="one"> <script type="text/javascript"> // 键盘抬起发送ajax jQuery实现 函数防抖 // $("#one").on("keyup",checkEmail()); // function checkEmail(){ // let timer=null; // return function (){ // clearTimeout(timer); // timer=setTimeout(function(){ // console.log('执行检查'); // },800); // } // } // 键盘抬起发送ajax原生js实现 函数防抖 let timeout = null onkeyup = function() { clearTimeout(timeout); timeout = setTimeout(function() { console.log("发送ajax") }, 1000); } document.getElementById("one").onkeyup = function() { console.log("键盘抬起") onkeyup() } </script>
当调用动作过n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间
<input type="text" id="one"> <script type="text/javascript"> // 窗口变化 ----- 函数防抖 var myFunc = function() { console.log("我要发送ajax") } var throttle = function(fn, delay){ let timer = null return function(){ let context = this, args = arguments clearTimeout(timer) timer = setTimeout(function(){ fn.apply(this, args) }, delay) }; }; window.onresize = throttle(myFunc, 1000); </script>
实现2
/** * 防抖函数 * @param {function} fn - 回调函数 * @param {number} wait - 定时防抖 * @param {boolean} immediate - 第一次触发回调事件就执行 */ const debounce = function(fn, wait = 500, immediate) { let timer = null return function(...args) { if (timer) clearTimeout(timer) // ------ 新增部分 start ------ // immediate 为 true 表示第一次触发后执行 // timer 为空表示首次触发 if (immediate && !timer) { fn.apply(this, args) } // ------ 新增部分 end ------ timer = setTimeout(() => { fn.apply(this, args) }, wait) } } // 模拟回调函数 const func = function(...args) { // your code ... console.log('fn 防抖执行了', ...args) } // 调用防抖 const betterFn = debounce(func, 1000, true)
加强版 throttle【感叹:大神的代码】
需求:函数防抖中:在用户操作非常频繁时,不等设置的延时时间结束就进行下次操作,会频繁的清除计时器并重新生成,所以函数fn
一直都没办法执行,导致用户操作迟迟得不到响应。
解决思路:
将函数节流和函数防抖合二为一,变成加强版的节流函数,关键在于wait
时间内,可以重新生成定时器,但是只要wait
的时间一到,必须给用户一个响应。此时不管用户是否还在频繁操作。
// fn 是需要节流处理的函数 // wait 是时间间隔 function throttle(fn, wait) { // previous 是上一次执行 fn 的时间 // timer 是定时器 let previous = 0, timer = null // 将 throttle 处理结果当作函数返回 return function (...args) { // 获取当前时间,转换成时间戳,单位毫秒 let now = +new Date() // ------ 新增部分 start ------ // 判断上次触发的时间和本次触发的时间差是否小于时间间隔 if (now - previous < wait) { // 如果小于,则为本次触发操作设立一个新的定时器 // 定时器时间结束后执行函数 fn if (timer) clearTimeout(timer) timer = setTimeout(() => { previous = now fn.apply(this, args) }, wait) // ------ 新增部分 end ------ } else { // 第一次执行 // 或者时间间隔超出了设定的时间间隔,执行函数 fn previous = now fn.apply(this, args) } } } // DEMO // 执行 throttle 函数返回新函数 const betterFn = throttle(() => console.log('fn 节流执行了'), 1000) // 第一次触发 scroll 执行一次 fn,每隔 1 秒后执行一次函数 fn,停止滑动 1 秒后再执行函数 fn document.addEventListener('scroll', betterFn)