如果只需要代码,就直接拿取最底部的一段代码即可
当我们使用window.resize mousemove等事件的时候,会非常频繁的触发,对于的函数,如果还要操作dom元素的,将会十分的消耗性能的,这时候就需要节流,防抖来解决。
函数防抖(debounce)
这里用一个例子,来进行讲解
假设我在一个输入框里输入内容,他会自动的根据输入框的内容给我显示搜索的结果。 但是这里的要求是在输入完成后,等待一段时间再触发搜索的功能,并且在这段时间还没到达的时候,如果在次进行输入,那么就要重新开始计时。不进行处理的显示
//html
<div class="container">
<input id="input" type="text">
<div id="content">
</div>
</div>
//js
//假设这是请求函数
function getData(value) {
console.log("runs");
return `搜索${value}的内容`;
}
const input = document.getElementById("input");
const content = document.getElementById("content");
input.oninput = function (e) {
content.innerText = getData(input.value);
};
显示结果。这里可以看到每进行一次输入就会触发,请求函数,如果是真实的api接口,就过度浪费
基础版防抖函数
这里是通过全局变量来存储setTimeout的id,再每次触发函数的时候,就请求之前的setTimeout(也可以用if来判断是否要清除),然后再开启一个新的setTimeout来执行函数
let debounce_timer;
/**
* 防抖函数
* @param {*} callback 需要运行的函数
* @param {*} duration 需要等待的时间
*/
function debounce(callback, duration) {
clearTimeout(debounce_timer);
debounce_timer = setTimeout(function () {
callback();
debounce_timer = null;
}, duration);
}
//请求函数
function getData(value) {
console.log("runs");
return `搜索${value}的内容`;
}
const input = document.getElementById("input");
const content = document.getElementById("content");
input.oninput = function (e) {
debounce(function () {
content.innerText = getData(input.value);
}, 1000);
};
最终结果,这里已经基本符合我们的要求了,但是采用这种方法也会带来一个问题就是全部变量的污染,所以接下来我们继续优化节流函数
进阶版防抖函数
首先,讲解一下高阶函数的定义,就是当一个函数的返回值是一个函数的话,那么它就是高阶函数
之前的节流函数的问题是会污染全局变量,于是我们就直接将,debounce_time变量,放在debounce函数内部,将功能函数给return出去。这里要注意被return出去的函数,再被调用的时候,依旧访问的是他debounce函数作用域里面的debounce_time变量。(这里需要学习一下,闭包与作用域的知识,我也会逐步更新的),所以每次清空的依旧是同一个debounce_time变量。采用了一个很巧妙的方法来解决的。
/**
* 防抖函数
* @param {*} callback 需要运行的函数
* @param {*} duration 需要等待的时间
* @returns 返回一个函数
*/
function debounce(callback, duration) {
var debounce_timer;
return function () {
clearTimeout(debounce_timer);
debounce_timer = setTimeout(function () {
callback();
debounce_timer = null;
}, duration);
};
}
//请求函数
function getData(value) {
console.log("runs");
return `搜索${value}的内容`;
}
const input = document.getElementById("input");
const content = document.getElementById("content");
// 这里不能放在input事件里面,否则返回的函数,就不是同一个作用域里面的了
let handleSearch = debounce(function () {
content.innerText = getData(input.value);
}, 1000);
input.oninput = function (e) {
handleSearch();
};
来看执行结果,依旧没有问题
但是这里还有最后一个问题,就是传参
再这段代码中,我们不处理debounce函数,假设我需要一些参数,来完善处理函数,直接传递是拿不到的
let handleSearch = debounce(function (res) {
console.log("我需要参数来处理,该函数,参数是" + res);
content.innerText = getData(input.value);
}, 1000);
input.oninput = function (e) {
handleSearch("参数1", "参数2");
};
我们继续完善debounce函数,拿不到参数是因为,给handleSearch传递的参数,最终是debounce函数中,return 的那个函数拿到的,而res的形参,需要debounce函数中的callback函数,传参才可以的
所以这里我们首先通过 debounce函数的 return 函数,的argument拿到传递的参数,(用arguments获取的原因,如果传递多个参数,可以直接用arguments获取到,而不用写多个形参),再讲arguments的值传递给callback函数,这里用apply的原因,apply的第二个参数是传递实参,并且是以数组的格式。
/**
* 防抖函数
* @param {*} callback 需要运行的函数
* @param {*} duration 需要等待的时间
* @returns 返回一个函数
*/
function debounce(callback, duration) {
var debounce_timer;
return function () {
let args = arguments;
clearTimeout(debounce_timer);
debounce_timer = setTimeout(function () {
callback.apply(null, args);
debounce_timer = null;
}, duration);
};
}
//请求函数
function getData(value) {
console.log("runs");
return `搜索${value}的内容`;
}
const input = document.getElementById("input");
const content = document.getElementById("content");
// 这里不能放在input事件里面,否则返回的函数,就不是同一个作用域里面的了
let handleSearch = debounce(function (res) {
console.log("我需要参数来处理,该函数,参数是" + res);
content.innerText = getData(input.value);
}, 1000);
input.oninput = function (e) {
handleSearch("参数1", "参数2");
};
这里看结果,最终拿到参数了的,这里是用的arguments来拿取,也可以通过解构的方法实现,更为简单
function debounce(callback, duration) {
var debounce_timer;
return function (...args) {
if (debounce_timer) {
clearTimeout(debounce_timer);
}
debounce_timer = setTimeout(function () {
callback(...args);
}, duration);
};
}
函数节流
函数节流是在固定的时间里执行一次
下面是一个输入框,我会不断地进行输入,但是我需要每过1s,才打印输入的内容
用时间戳实现函数节流
// 首先获取到input,并且给他绑定键盘点击事件,然后,打印输入的内容
const input = document.getElementById("input");
let res = throttle(() => {
console.log(input.value);
}, 2000);
input.onkeydown = function () {
res();
};
//节流函数
function throttle(callback, duration) {
let defaultTime = 0; //定义最开始默认的时间
return function (...args) {
var nowTime = new Date(); //获取当前时间戳
if (nowTime - defaultTime > duration) {
// 如果当前时间戳,前去之前的时间大于我们传递的等待时间,就可以执行代码
callback(...args);
defaultTime = nowTime;
}
};
}
计时器实现
function throttle(callback, duration) {
let timer; //用于记录计时器
return function (...args) {
if (!timer) {
//如果计时器为空,代表duration时间到了,可以执行
callback(...args);
timer = setTimeout(() => {
timer = null;//当duration时间到达,清空timer
}, duration);
}
};
}