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