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

深入探讨防抖函数中的 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.kler.cn/a/522287.html

相关文章:

  • C++ 中用于控制输出格式的操纵符——setw 、setfill、setprecision、fixed
  • 71-《颠茄》
  • go gin配置air
  • 分布式版本控制系统:Git
  • 58.界面参数传递给Command C#例子 WPF例子
  • 机器学习(三)
  • 论文笔记(六十五)Schmidt-EKF-based Visual-Inertial Moving Object Tracking
  • LeetCode-175. 组合两个表
  • H2 Database安装部署
  • VMware 中Ubuntu无网络连接/无网络标识解决方法【已解决】
  • PHP Error处理与优化指南
  • volatile之四类内存屏障指令 内存屏障 面试重点 底层源码
  • 多模态论文笔记——TECO
  • 已解决:Win10任务状态栏卡死点击无响应的解决方案
  • 【SAP-PP】生产订单和计划订单
  • DeepSeek-R1试用
  • AI在Facebook平台中的安全应用探索
  • JAVA 接口、抽象类的关系和用处 详细解析
  • Python-基础环境(01) 虚拟环境,Python 基础环境之虚拟环境,一篇文章助你完全搞懂!
  • 通过案例研究二项分布和泊松分布之间关系(2)
  • Lucene中DocValues 和 Stored Fields 的用法
  • 【Unity3D】Tilemap俯视角像素游戏案例
  • 字节启动AGI长期研究计划,代号Seed Edge
  • Nacos未授权新建用户漏洞(/nacos/v1/auth/users)
  • 深入理解JWT及其应用
  • 简易CPU设计入门:控制总线的剩余信号(一)