学习链接

函数节流和防抖

40+ 大厂常考 JS 手写题系列【摸鱼必备】

一、函数节流(throttle)

函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次
有个需要频繁触发函数,出于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效。

版本一

  1. function throttle(func, delay = 500) {
  2. // 记录上一次函数触发的时间
  3. let lastTime = 0; // 设为0 使得第一次可直接触发
  4. return function(...args) {
  5. // 记录当前函数触发的时间
  6. let nowTime = Date.now();
  7. if (nowTime - lastTime > delay) {
  8. // 修正this指向问题
  9. func.apply(this, args);
  10. // 同步时间
  11. lastTime = nowTime;
  12. }
  13. }
  14. }

测试

  1. html,body {
  2. /* 使得滚动条出现 */
  3. height: 500%;
  4. }
  1. function throttle(func, delay = 500) {
  2. // 记录上一次函数触发的时间
  3. let lastTime = 0; // 设为0 使得第一次可直接触发
  4. return function(...args) {
  5. // 记录当前函数触发的时间
  6. let nowTime = Date.now();
  7. if (nowTime - lastTime > delay) {
  8. // 修正this指向问题
  9. func.apply(this, args);
  10. // 同步时间
  11. lastTime = nowTime;
  12. }
  13. }
  14. }
  15. document.onscroll = throttle(function() {
  16. console.log('scroll事件被触发了' + Date.now());
  17. }, 1500);

版本二

  1. // options.leading 来表示是否可以立即执行一次,
  2. // opitons.trailing 表示结束调用的时候是否还要执行一次,默认都是 true。
  3. // 不能同时将 leading 或 trailing 设置为 false 否则不会执行
  4. function throttle(func, delay, options) {
  5. let timer, context, args;
  6. let lastTime = 0;
  7. if (!options) options = {};
  8. const later = function() {
  9. lastTime = options.leading === false ? 0 : new Date().getTime();
  10. timer = null;
  11. func.apply(context, args);
  12. };
  13. const throttled = function(...args) {
  14. const nowTime = new Date().getTime();
  15. if (!lastTime && options.leading === false) { // 用===false 使得默认情况为true
  16. lastTime = nowTime; // 开始时不调用
  17. }
  18. const remaining = delay - (nowTime - lastTime); // 本轮节流剩余时间
  19. context = this; // later中使用
  20. // <=0: 间隔已经超过节流时间 可执行
  21. // >delay: 执行later后更新lastTime可能导致的情况 即重新启动计时执行第一次
  22. if (remaining <= 0 || remaining > delay) {
  23. if (timer) {
  24. clearTimeout(timer); // 未结束调用 清空timer
  25. timer = null;
  26. }
  27. lastTime = nowTime; // 更新上一次的时间
  28. func.apply(context, args);
  29. } else if (!timer && options.trailing !== false) { // 用!==false 使得默认情况为true
  30. timer = setTimeout(later, remaining); // 结束时调用
  31. }
  32. };
  33. return throttled;
  34. }
  35. document.onscroll = throttle(function() {
  36. console.log('scroll事件被触发了' + Date.now());
  37. }, 1500);

二、函数防抖(debounce)

防抖函数:一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效。

版本一

  1. function debounce(func, delay = 300) {
  2. // 存储计时器
  3. let timer = null;
  4. return function(...args) {
  5. // 清除计时器
  6. clearTimeout(timer);
  7. // 重新计时
  8. timer = setTimeout(() => {
  9. func.apply(this, args);
  10. }, delay);
  11. }
  12. }

测试

  1. <button id="btn">按钮</button>
  2. <script type="text/javascript">
  3. function debounce(func, delay = 300) {
  4. // 存储计时器
  5. let timer = null;
  6. return function(...args) {
  7. // 清除计时器
  8. clearTimeout(timer);
  9. // 重新计时
  10. timer = setTimeout(() => {
  11. console.log('this: ', this);
  12. func.apply(this, args);
  13. }, delay);
  14. // 或者
  15. // // 固定this指向
  16. // const context = this;
  17. // // 重新计时
  18. // timer = setTimeout(function() {
  19. // console.log('this: ', this);
  20. // console.log('context: ', context);
  21. // func.apply(context, args);
  22. // }, delay);
  23. }
  24. }
  25. document.getElementById('btn').onclick = debounce(function() {
  26. console.log('点击事件被触发' + Date.now())
  27. }, 1000)
  28. </script>

版本二

immediate 是否可立即执行一次。

  1. <button id="btn">按钮</button>
  2. <button id="cancelBtn">取消</button>
  3. <script type="text/javascript">
  4. function debounce(func, wait = 500, immediate = false) {
  5. let timeout = null, result = null;
  6. const debounced = function (...args) {
  7. // 固定this指向
  8. let context = this;
  9. // 清除计时器
  10. clearTimeout(timeout);
  11. if (immediate) { // 是否立即执行
  12. // 如果已经执行过,不再执行
  13. let callNow = !timeout;
  14. timeout = setTimeout(function(){
  15. timeout = null;
  16. }, wait)
  17. // 第一次立即执行
  18. if(callNow) result = func.apply(context, args);
  19. } else {
  20. timeout = setTimeout(function(){
  21. result = func.apply(context, args)
  22. }, wait);
  23. }
  24. return result;
  25. };
  26. debounced.cancel = function() {
  27. clearTimeout(timeout);
  28. timeout = null;
  29. };
  30. return debounced;
  31. }
  32. const setUseAction = debounce(function() {
  33. console.log('点击事件被触发' + Date.now())
  34. }, 1000, true);
  35. // 使用防抖
  36. document.getElementById('btn').onclick = setUseAction;
  37. // 取消定时器
  38. document.getElementById('cancelBtn').onclick = setUseAction.cancel;
  39. </script>