节流:阻止函数被动的频繁调用

483 阅读3分钟

函数节流主要是解决非用户操作导致的函数被频繁调用的问题。

节流背景

浏览器中一些事件让函数非常频繁地调用,从而造成大的性能问题。

  • window.onresize 事件。当浏览器窗口大小被拖动而改变的时候,这个事件触发的频率非常之高。
    如果我们在 window.onresize 事件函数里有一些跟 DOM 节点相关的操作,这时候浏览器可能就会吃不消而造成卡顿现象。
  • mousemove 事件。同样,如果我们给一个 div 节点绑定了拖曳事件(主要是 mousemove),当 div 节点被拖动的时候,也会频繁地触发该拖曳事件函数。
  • 上传进度。一些上传插件在真正开始上传文件之前,会对文件进行扫描并随时通知 JavaScript 函数,以便在页面中显示当前的扫描进度。通知的频率非常之高,大约一秒钟 10 次,很显然我们在页面中不需要如此频繁地去提示用户。

解决方案

其实上面的问题在于,函数被触发的频率太高,1s可能运行了 10 次,但显然我们可能500ms运行1次就够

function throttle(fn, delay = 500) {
  // 记录上一次运行时间,初始设置0
  let lastDate = 0;
  return function(...args) {
    let now = new Date() - 0;
    // 当次运行的时间和上一次时间的差值,超过500ms运行,否则不运行
    if (now - lastDate < delay) {
      return;
    }
    // 迭代上一次运行时间
    lastDate = now;
    fn.call(null, ...args);
  };
}

实现逻辑


// 1. 普通的函数
function print() {
  console.log(new Date());
}


let delay = 1000;
let lastDate = 0;

function print1() {
  let now = new Date() - 0;
  if (now - lastDate < delay) {
    return;
  }
  lastDate = now;
}
print1();
print1();

// 2. 相关内容封装
var print2 = (function() {
  let delay = 1000;
  let lastDate = 0;

  return function() {
    let now = new Date() - 0;
    if (now - lastDate < delay) {
      return;
    }
    lastDate = now;
    console.log(new Date());
  };
})();
print2();
print2();

// 3. 提取参数
var print3 = function(fn, delay = 1000) {
  let lastDate = 0;

  return function() {
    let now = new Date() - 0;
    if (now - lastDate < delay) {
      return;
    }
    lastDate = now;
    fn.apply(null, arguments);
  };
};
function print() {
  console.log(new Date());
}
let print3Demo = print3(print, 1000);
print3Demo();
print3Demo();

// 4. 再抽象一层
function throttle(fn, delay = 500) {
  let lastDate = 0;
  return function(...args) {
    let now = new Date() - 0;
    if (now - lastDate < delay) {
      return;
    }
    lastDate = now;
    fn.call(null, ...args);
  };
}
// es6
const throttle = (fn, delay = 500) => {
  let lastDate = 0
  return (...args) => {
    let now = new Date() - 0
    if (now - lastDate < delay) {
      return
    }
    lastDate = now
    fn(args)
  }
}

function print(name) {
  console.log(name, new Date());
}
var tprint = throttle(print, 100);

for (let i = 0; i < 10000000; i++) {
  tprint(1);
}

// 使用场景:
window.onresize = throttle(function() {
  console.log(1);
}, 500);

《JavaScript设计模式与开发实践》

本文主要在看《JavaScript设计模式与开发实践》,再进行总结的,作者写的很棒,作者写的throttle

// throttle 函数的原理是,将即将被执行的函数用 setTimeout 延迟一段时间执行。如果该次延迟执行还没有完成,则忽略接下来调用该函数的请求。throttle 函数接受 2 个参数,第一个参数为需要被延迟执行的函数,第二个参数为延迟执行的时间。
var throttle = function(fn, interval) {
  var __self = fn, // 保存需要被延迟执行的函数引用
    timer, // 定时器
    firstTime = true; // 是否是第一次调用
  return function() {
    var args = arguments,
      __me = this;
    if (firstTime) {
      // 如果是第一次调用,不需延迟执行
      __self.apply(__me, args);
      return (firstTime = false);
    }
    if (timer) {
      // 如果定时器还在,说明前一次延迟执行还没有完成
      return false;
    }
    timer = setTimeout(function() {
      // 延迟一段时间执行
      clearTimeout(timer);
      timer = null;
      __self.apply(__me, args);
    }, interval || 500);
  };
};

window.onresize = throttle(function() {
  console.log(1);
}, 500);