当前位置: 首页 > news >正文

深入探讨防抖函数中的 this 上下文

深入剖析防抖函数中的 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. 顺序问题:为什么 1 打印完成后没有紧接着打印 2,而是先打印了 11 ?并且为什么输出顺序不是 2 -> 3 -> 22 -> 33 呢?

为了彻底搞清楚这些现象,下面是对知识点的解析。

关键知识点解析

1. 函数定义时的 this(打印 111 处)

debouncedebounce2 函数的定义里,console.log(1, 'this:', this)console.log(11, 'this:', this) 中的 this 指向取决于函数的调用方式。由于这两个函数是直接定义在全局作用域下的,并没有通过某个对象来调用,所以在 JavaScript 中,this 默认指向全局对象 Window(在 Node.js 环境中则是 global)。

2. 内部返回函数中的 this(打印 222 处)

return 的匿名函数中,console.log(2, 'this:', this)console.log(22, 'this:', this) 里的 this 指向是由调用时的上下文决定的。
当我们执行 obj.debouncedSayName()obj.debouncedSayName2() 时,这两个函数是作为 obj 对象的方法被调用的。根据 JavaScript 的规则,当函数作为对象的方法调用时,this 会指向调用该方法的对象,所以这两个地方的 this 都指向 obj

3. 定时器中的 this(打印 333 处)

  • 箭头函数中的 thisconsole.log(3, 'this:', this):在 debounce 函数的定时器回调中使用了箭头函数。箭头函数有一个重要的特性,就是它不会绑定自己的 this,而是继承自定义它的外部函数(这里是匿名函数)的 this。因此,这里的 this 仍然指向 obj
  • 普通函数中的 thisconsole.log(33, 'this:', this):在 debounce2 函数的定时器回调中使用的是普通函数。普通函数的 this 在调用时会动态绑定,而在定时器中,普通函数的 this 默认指向全局对象 Window(在 Node.js 环境中是 timeout)。

顺序问题解析

观察输出顺序:1 -> 11 -> 2 -> 22 -> 3 -> 33

为什么不是 1 -> 2

当我们定义 obj 对象时,会通过 debouncedebounce2 函数生成 debouncedSayNamedebouncedSayName2 属性。在这个过程中,debouncedebounce2 函数会被调用,于是就会执行到 console.log(1, 'this:', this)console.log(11, 'this:', this)。而 debouncedebounce2 返回的内部函数,要等到调用 obj.debouncedSayName()obj.debouncedSayName2() 时才会执行。

为什么不是 2 -> 3 -> 22 -> 33

JavaScript 是单线程的编程语言,而 setTimeout 是异步操作。当遇到 setTimeout 时,它会将回调函数推入事件队列,等待主线程的同步任务全部执行完毕后再执行。因此,debouncedSayNamedebouncedSayName2 的同步部分会先执行,分别打印 222,然后才会将定时器的回调函数放入事件队列。最后,定时器的回调函数依次执行,分别打印 333

总结

通过这个例子,我们可以得出以下重要结论:

1. 箭头函数与普通函数的区别

  • 箭头函数:箭头函数中的 this 继承自外层函数,这使得它非常适合用于需要保留上下文的场景。
  • 普通函数:普通函数的 this 根据调用方式动态绑定,在不同的调用场景下,this 的指向可能会发生变化。

2. 异步任务的执行顺序

异步任务会被推入事件队列,只有当主线程的同步任务全部完成后,才会依次执行事件队列中的异步任务。

3. this 的指向受调用方式影响

如果函数作为对象的方法调用,this 会指向该对象;如果函数没有通过对象调用,this 通常指向全局对象(在严格模式下为 undefined)。

希望通过这篇文章,你能更清晰地理解防抖函数中的 this 机制,在实际开发中避免因 this 指向问题而产生的错误。

http://www.lryc.cn/news/527426.html

相关文章:

  • 【AI论文】魔鬼在细节:关于在训练专用混合专家模型时实现负载均衡损失
  • Gurobi基础语法之addVar 和 addVars
  • C语言学习阶段性总结(五)---函数
  • K8S 快速实战
  • java后端之事务管理
  • 【Redis】缓存+分布式锁
  • 二分查找题目:寻找两个正序数组的中位数
  • 网络安全 | F5-Attack Signatures详解
  • Redis --- 分布式锁的使用
  • LeetCode100之全排列(46)--Java
  • goframe 博客分类文章模型文档 主要解决关联
  • 【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南
  • 安卓日常问题杂谈(一)
  • Kitchen Racks 2
  • 嵌入式学习笔记-杂七杂八
  • 14-7C++STL的stack容器
  • Vue 3 中的响应式系统:ref 与 reactive 的对比与应用
  • 物业巡更系统助推社区管理智能化与服务模式创新的研究与应用
  • windows蓝牙驱动开发-生成和发送蓝牙请求块 (BRB)
  • Linux网络之序列化和反序列化
  • linux设置mysql远程连接
  • react-native网络调试工具Reactotron保姆级教程
  • erase() 【删数函数】的使用
  • 性能测试丨内存火焰图 Flame Graphs
  • AIGC的企业级解决方案架构及成本效益分析
  • Linux 入门 常用指令 详细版
  • 【R语言】流程控制
  • 猿人学第一题 js混淆源码乱码
  • 计算机组成原理(2)王道学习笔记
  • 【AI日记】25.01.26