深圳网站制作长沙如何申请域名
深入剖析防抖函数中的 this 上下文
 
最近我在研究防抖函数实现的时候,发现一个耗费脑子的问题,出现了令我困惑的问题。接下来,我将通过代码示例,深入探究这些现象背后的原理。
示例代码
function debounce(fn, delay) {let timer = null;console.log(1, 'this:', this);return function(...args) {console.log(2, 'this:', this);if (timer) clearTimeout(timer);timer = setTimeout(() => {console.log(3, 'this:', this);  fn.apply(this, args);}, delay);};
}function debounce2(fn, delay) {  let timer = null;console.log(11, 'this:', this);return function(...args) {  console.log(22, 'this:', this);if (timer) clearTimeout(timer);timer = setTimeout(function() {console.log(33, 'this:', this);fn.apply(this, args);}, delay);};
}let obj = {name: 'John',sayName: function() {console.log(this.name);},debouncedSayName: debounce(function() {this.sayName();}, 300),debouncedSayName2: debounce2(function() {this.sayName();}, 300)
};obj.debouncedSayName();
obj.debouncedSayName2();
 
现象与问题
运行上述代码后(假设在浏览器环境中),会得到如下输出:
 
这里有一个顺序问题让我困惑了一下:
- 顺序问题:为什么 
1打印完成后没有紧接着打印2,而是先打印了11?并且为什么输出顺序不是2 -> 3 -> 22 -> 33呢? 
为了彻底搞清楚这些现象,下面是对知识点的解析。
关键知识点解析
1. 函数定义时的 this(打印 1 和 11 处)
 
在 debounce 和 debounce2 函数的定义里,console.log(1, 'this:', this) 和 console.log(11, 'this:', this) 中的 this 指向取决于函数的调用方式。由于这两个函数是直接定义在全局作用域下的,并没有通过某个对象来调用,所以在 JavaScript 中,this 默认指向全局对象 Window(在 Node.js 环境中则是 global)。
2. 内部返回函数中的 this(打印 2 和 22 处)
 
在 return 的匿名函数中,console.log(2, 'this:', this) 和 console.log(22, 'this:', this) 里的 this 指向是由调用时的上下文决定的。
 当我们执行 obj.debouncedSayName() 和 obj.debouncedSayName2() 时,这两个函数是作为 obj 对象的方法被调用的。根据 JavaScript 的规则,当函数作为对象的方法调用时,this 会指向调用该方法的对象,所以这两个地方的 this 都指向 obj。
3. 定时器中的 this(打印 3 和 33 处)
 
- 箭头函数中的 
this(console.log(3, 'this:', this)):在debounce函数的定时器回调中使用了箭头函数。箭头函数有一个重要的特性,就是它不会绑定自己的this,而是继承自定义它的外部函数(这里是匿名函数)的this。因此,这里的this仍然指向obj。 - 普通函数中的 
this(console.log(33, 'this:', this)):在debounce2函数的定时器回调中使用的是普通函数。普通函数的this在调用时会动态绑定,而在定时器中,普通函数的this默认指向全局对象Window(在 Node.js 环境中是timeout)。 
顺序问题解析
观察输出顺序:1 -> 11 -> 2 -> 22 -> 3 -> 33
为什么不是 1 -> 2 ?
 
当我们定义 obj 对象时,会通过 debounce 和 debounce2 函数生成 debouncedSayName 和 debouncedSayName2 属性。在这个过程中,debounce 和 debounce2 函数会被调用,于是就会执行到 console.log(1, 'this:', this) 和 console.log(11, 'this:', this)。而 debounce 和 debounce2 返回的内部函数,要等到调用 obj.debouncedSayName() 和 obj.debouncedSayName2() 时才会执行。
为什么不是 2 -> 3 -> 22 -> 33 ?
 
JavaScript 是单线程的编程语言,而 setTimeout 是异步操作。当遇到 setTimeout 时,它会将回调函数推入事件队列,等待主线程的同步任务全部执行完毕后再执行。因此,debouncedSayName 和 debouncedSayName2 的同步部分会先执行,分别打印 2 和 22,然后才会将定时器的回调函数放入事件队列。最后,定时器的回调函数依次执行,分别打印 3 和 33。
总结
通过这个例子,我们可以得出以下重要结论:
1. 箭头函数与普通函数的区别
- 箭头函数:箭头函数中的 
this继承自外层函数,这使得它非常适合用于需要保留上下文的场景。 - 普通函数:普通函数的 
this根据调用方式动态绑定,在不同的调用场景下,this的指向可能会发生变化。 
2. 异步任务的执行顺序
异步任务会被推入事件队列,只有当主线程的同步任务全部完成后,才会依次执行事件队列中的异步任务。
3. this 的指向受调用方式影响
 
如果函数作为对象的方法调用,this 会指向该对象;如果函数没有通过对象调用,this 通常指向全局对象(在严格模式下为 undefined)。
希望通过这篇文章,你能更清晰地理解防抖函数中的 this 机制,在实际开发中避免因 this 指向问题而产生的错误。
