在 JavaScript 中,this 关键字的绑定取决于函数的调用方式(函数调用时的上下文),而不是函数的定义位置。
在节流和防抖函数中,this 的作用主要是确保被节流或防抖的函数在执行时具有正确的上下文。 如果不使用 this,而是直接调用传递进来的函数 fn,那么函数在执行时可能会丢失其原始上下文,导致意料之外的行为或错误。
具体来说,在节流和防抖函数中使用 this 的原因有以下几点:
1)保持执行上下文:
在 JavaScript 中,函数的执行上下文(也称为作用域)是很重要的。通过使用 this,我们确保执行被节流或防抖的函数时,其执行上下文与原始调用的上下文一致。
2)访问对象属性和方法:
【在前两个非对象例子无差别,最后一个例子的三种调用均输出count=NaN】
如果被节流或防抖的函数是对象的方法,那么在函数内部可能需要访问该对象的属性或方法。使用 this 可以确保在函数内部正确地访问到对象的属性和方法。
3)提高可复用性:
使用 this 可以使节流和防抖函数更具有通用性和可复用性,因为它们可以适用于不同的上下文和对象。
function throttle(fn, delay) {
let lastTime = 0; // 记录上次执行函数的时间戳
return function (...args) {
const context = this; // 显式保存函数执行上下文
const currentTime = Date.now(); // 获取当前时间戳
if (currentTime - lastTime >= delay) {
// 如果当前时间距离上次执行函数的时间大于等于设定的延迟时间
fn.apply(context, args); // 执行函数
// fn.call(this, ...args); // 直接绑定this,该this是rerturn函数的this
lastTime = currentTime; // 更新上次执行函数的时间戳
}
};
}
function debounce(fn, delay) {
let timer; // 定时器变量
return function (...args) {
const context = this; // 显式保存函数执行上下文
clearTimeout(timer); // 清除之前设置的定时器
timer = setTimeout(() => {
// clearTimeout(timer);
fn.apply(context, args); // 执行函数
// fn.call(this, ...args); // 直接绑定this也可以
}, delay);
};
}
function debounce(func, wait) {
let timer;
return function (...args) {
const later = () => {
clearTimeout(timeout); // 确保定时器在执行函数之前被清除。这通常是一个防御性的编程习惯,确保定时器在执行目标函数之前已经被清除,避免潜在的重复执行。
func.apply(this, args); // 直接使用this
};
clearTimeout(timer); // 清除之前设置的定时器
timer = setTimeout(later, wait);
};
}
用例
// 1、不带参数用例:
// throttle使用示例
function throttledFunction() {
console.log("Throttled function executed!");
}
const throttled = throttle(throttledFunction, 1000); // 创建一个每隔1秒执行一次的节流函数
// 模拟频繁调用
for (let i = 0; i < 10; i++) {
setTimeout(() => throttled(), i * 200); // 在200毫秒的间隔内调用throttled函数
}
// debounce使用示例
function debouncedFunction() {
console.log("Debounced function executed!");
}
const debounced = debounce(debouncedFunction, 1000); // 创建一个在最后一次调用后延迟1秒执行的防抖函数
// 模拟频繁调用
for (let i = 0; i < 10; i++) {
setTimeout(() => debounced(), i * 200); // 在200毫秒的间隔内调用debounced函数
}
// 2、带参数用例:
// 定义一个带参数的函数 foo
function foo(message) {
console.log("Function executed with message:", message);
}
const throttledFoo = throttle(foo, 1000); // 1000 毫秒的节流
const debouncedFoo = debounce(foo, 1000); // 1000 毫秒的防抖
throttledFoo("Hello, Throttle!"); // 这个调用会立即执行 foo,同时传递参数;第二次调用等待 1000 毫秒后执行,同时传递参数
debouncedFoo("Hello, Debounce!"); // 这个调用将在最后一次调用之后的 1000 毫秒后执行 foo,并且传递参数
// 3、作为对象方法并带参数用例:
// 定义包含方法的对象
const obj = {
count: 0, // 一个计数器属性
objMethod(message) {
console.log("Method executed with message:", message, "Count:", this.count++);
}
};
// throttledObjMethod1 和 debouncedObjMethod1 通过 .bind(obj) 确保了 obj.objMethod 在 obj 上下文中执行,可以正确访问 obj 的属性和方法。
const throttledObjMethod1 = throttle(obj.objMethod, 1000).bind(obj); // 1000 毫秒的节流,并绑定上下文为 obj,.bind(obj)不可省略
const debouncedObjMethod1 = debounce(obj.objMethod, 1000).bind(obj); // 1000 毫秒的防抖,并绑定上下文为 obj,.bind(obj)不可省略
// 这个调用会立即执行 objMethod,同时传递参数;第二次调用等待 1000 毫秒后执行,同时传递参数
throttledObjMethod1("Hello, Throttle!"); // Method executed with message: Hello, Throttle! Count: 0
// 这个调用不会立即执行,而是将在最后一次调用之后的 1000 毫秒后执行 objMethod,并且传递参数
debouncedObjMethod1("Hello, Debounce!"); // Method executed with message: Hello, Debounce! Count: 1
// throttledObjMethod2 和 debouncedObjMethod2 返回的函数如果没有绑定上下文,则 this 的值取决于函数调用的方式。
const throttledObjMethod2 = throttle(obj.objMethod, 1000);
const debouncedObjMethod2 = debounce(obj.objMethod, 1000);
// 如果直接调用返回的函数,this 会指向全局对象(在浏览器中是 window,在 Node.js 中是 global),或者在严格模式下是 undefined。
throttledObjMethod2("Hi, Throttle!"); // Method executed with message: Hi, Throttle! Count: NaN
debouncedObjMethod2("Hi, Debounce!"); // Method executed with message: Hi, Debounce! Count: NaN
// 当通过 obj 对象调用这两个方法时,this 将指向 obj。这是因为在 JavaScript 中,对象方法的调用上下文由调用者决定。
obj.objMethod_throttle = throttle(obj.objMethod, 1000);
obj.objMethod_debounce = debounce(obj.objMethod, 1000);
// obj.objMethod_throttle 和 obj.objMethod_debounce 被调用时,this 指向 obj。obj.objMethod 中的 this 指向 obj,所以 this.count++ 正确更新。
obj.objMethod_throttle("Hello, Throttle!"); // Method executed with message: Hello, Throttle! Count: 2
obj.objMethod_debounce("Hello, Debounce!"); // Method executed with message: Hello, Debounce! Count: 3