节流:阻止函数被动的频繁调用
函数节流主要是解决非用户操作导致的函数被频繁调用的问题。
节流背景
浏览器中一些事件让函数非常频繁地调用,从而造成大的性能问题。
- 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);