这篇文章的重点不在防抖和节流概念的描述,而是记录防抖和节流中让我困惑的逻辑,包括 arguments 对象。如有错误,恳请大佬指正。
防抖
var debounce = function(func, delay) {
var timer = null;
return function(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
};
window.addEventListener("scroll", debounce(func, delay));
- addEventListener 的第二个参数是一个返回 scroll 事件要绑定的回调函数 的函数。scroll 事件需要绑定一个函数,所以 debounce 执行返回一个函数,绑定在 scroll 事件上。
- addEventListener 绑定是一次性的。debounce 只在 scroll 事件回调被绑定的时候执行了一次,所以 timer 全局都只有一个。回调函数是闭包,所以能访问到 timer。
- scroll 事件的回调函数要接收的参数用 ES6 中的 rest 参数统一存储到变量 args 中。
- 为什么 func 中要用 apply 保留 this
- 业务函数直接做回调的 this 不能因为加了一层 debounce 而有所改变。
- scroll 事件的回调函数的 this 和 debounce 返回的函数中的 this 相同,也即 setTimeout 函数中的 this。
- 如果 setTimeout 函数的回调函数是箭头函数,那么 func 的 this 和 setTimeout 函数的 this 相同,直接使用即可。如果是普通函数就需要外部保存一下再传进来使用(throttle的定时器写法)。
节流
注意点和防抖函数相同
时间戳实现
var throttle = (func, wait = 50) => {
let lastTime = 0;
return function(...args) {
let now = +new Date();
if (now - lastTime > wait) {
lastTime = now;
func.apply(this, args);
}
};
};
定时器实现
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
};
};
function handle() {
// 业务逻辑函数
console.log(Math.random());
}
window.addEventListener("scroll", throttle(handle, 1000));
arguments
概念
《js 高程 3.7.1 理解参数》 arguments 和形参的关系
- 通过 arguments 可以获取到所有传递给函数的参数,无论是否有形参接收。所以命名参数并不是必须的。
- arguments 是个类数组,有 length 属性,可以通过下标访问
- 命名参数被修改,更新会同步到 arguments 相应位置(只在非严格模式下)。但它们并不访问相同的内存空间,只是值会同步。
- 箭头函数里没有 arguments
- 所有参数都通过值传递,不可能传递引用。
- 即给一个函数传一个对象,函数中修改这个对象的引用不会影响之前的对象。修改对象中属性的值会影响之前的对象。
- 之前的对象的指针复制了一份,给了这个参数。如果把这个参数的引用改了,那么就和之前的对象无关了。
项目中的应用
- 代码中的 arguments 是 scroll 监听传给回调函数的参数。
- 下面有多个debounce函数,区别在于arguments的写法。每个函数按照P1 P2 P3...的顺序看注释,都在解释arguments。
// html
<input type="text" id="inputDom">
<div id="list"></div>
// js
var inputDom = document.getElementById('inputDom');
inputDom.addEventListener("keyup", debounce(searchHandle));
const globalVar = 'globalVar';
/* 业务函数 */
function searchHandle(e, dom, data) {
console.log(e);
console.log(dom);
console.log(data);
}
function debounce(func) {
let listDom = document.getElementById("list");
var timer = null;
return function () {
console.log(arguments);// keyup监听事件,默认传递给回调函数的参数。
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {// setTimeout() 和 第一个参数即回调函数 是不同的两层函数。// 这个回调函数没有默认传进来的arguments,因为它并不是被监听事件调用的。
console.log(arguments);
func(arguments[0][0], listDom, globalVar);
}, 300, arguments);
};
}
function debounce1(func) {
return function () {
console.log(arguments);// P1. keyup监听事件,默认传递给回调函数的参数。
// ...略
timer = setTimeout(function () {
console.log(arguments);// P3. 这里是arguments对象。arguments[0]即传进来的P2
func(arguments[0][0], tsgDom, mapTSG); // P4. arguments对象。arguments[0][0]是P1处arguments[0],即事件监听event。
}, 300, arguments);// P2. 这个arguments是P1处的arguments,但是在这里只是个普通的命名参数,不是arguments对象。
};
}
function debounce2(func) {
return function () {
console.log(arguments);// P1.
var myParam = arguments;
// ...略
timer = setTimeout(function (myParam2) { // P3. 这里是回调函数接受的参数
console.log(arguments);// P4. arguments[0]即myParam2
func(arguments[0][0], tsgDom, mapTSG); // P5.
func(myParam2[0], tsgDom, mapTSG); // P6.
}, 300, myParam);// P2. 这里是setTimeout传递给回调函数的参数。
};
}
function debounce3(func) {
return function () {
console.log(arguments);// P1.
var myParam = arguments;
// ...略
timer = setTimeout(function () { // P3. 这里不接收
console.log(arguments);// P4. 这里arguments的长度为0,因为该回调并没有接收到任何参数。
func(myParam[0], tsgDom, mapTSG); // P5. 这里仍可以访问到myParam,因为闭包。
}, 300);// P2. 这里不传递
};
}
没有防抖节流,直接传参
window.addEventListener("scroll", function () {
var otherParam = 'otherParam';
searchHandle(arguments[0], otherParam); // arguments[0]是event
})
调用逻辑函数,需绑定 this
window.addEventListener("scroll", function () {
var otherParam = 'otherParam';
searchHandle.call(this, arguments[0], otherParam); // arguments[0]是event
})