JavaScript this 指向绑定方式及不适用情况详解

作者:半夏

时间:2026-10-26

浏览:573

本文详细解析 JavaScript 中 this 的四种绑定规则:默认绑定、隐式绑定、显式绑定(call/apply)及 new 绑定,并通过防抖函数实例说明调用位置对 this 的影响。同时指出 ES6 箭头函数不适用上述规则,其 this 由外层作用域决定,帮助开发者准确掌握 this 指向逻辑。

文章目录

前言

对于刚接触 Web 前端开发的开发者而言,JavaScript 中的 this 指向往往是一个难点。本文旨在记录近期在研究匿名函数中 this 指向问题时产生的思考与心得。

问题复现

最近在研究函数防抖场景时,观察到如下代码实现:

function debounce(fn, delay) {
  var timer; // 维护一个 timer
  return function () {
    var _this = this; // 取 debounce 执行作用域的 this
    var args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      fn.apply(_this, args); // 用 apply 指向调用 debounce 的对象,相当于 _this.fn(args);
    }, delay);
  };
}

代码中包含 var _this = this 这一行。该代码位于由 return 返回的匿名函数内部,这引发了我的疑惑:依据我对 JavaScript 基础知识的理解,此处的 this 理应指向全局作用域,为何却能如注释所示,指向 debounce 执行时的作用域?直觉上,是否应该采用其他写法更为合理?(事实证明这种直觉写法是错误的)

function debounce(fn, delay) {
    var timer
    var _this = this
    return function() {
    ...
    }
}

因此,我计划通过代码实测 debounce 函数执行作用域内的 this 具体指向何物,以及其是否会发生变化。基于我的理解,此类匿名函数中的 this 是否均指向全局对象?为此,我编写了如下代码(关键部分):

在 HTML 的 body 部分添加了一个 button 标签。

<button>我是button</button>

在 script 标签内编写了如下逻辑:

//函数防抖
function debounce(fn, delay) {
  var timer; // 维护一个 timer
  return function () {
    var _this = this; // 取 debounce 执行作用域的 this
    var args = arguments;
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(function () {
      fn.apply(_this, args); // 用 apply 指向调用 debounce 的对象,相当于 _this.fn(args);
    }, delay);
  };
}
var btn = document.getElementsByTagName('button')[0]
btn.onclick= debounce(function() {
  console.log(this)
}, 1000)

点击按钮后观察控制台输出,依据之前的理解,预期的 this 应当是 window 全局对象。

<button>我是button</button>

然而实际输出却是 button 元素。为了进一步探究,我在脚本中增加了一个页面点击事件绑定:

window.onclick = debounce(function() {
 &nbsp;console.log(this)
}, 1000)

点击页面空白处后,控制台输出如下:

前言

此次输出结果为 window。由此可见,this 的实际指向与 debounce 函数所返回函数的调用者密切相关。第一次控制台输出 button 元素,是因为该返回函数是通过 button 元素调用的;第二次调用者变为 window,因此输出 window。

btn.onclick = dobounce(function() {console.log(this)}, 1000)

以下代码示例进一步阐释了这一机制:

  • 页面初始化完成后执行脚本,debounce 函数接收一个具体函数(暂命名为 fn)和一个时间间隔参数(interval)。
  • 进入 debounce 函数内部,它 return 一个匿名函数,并将其赋值给 btn.onclick,这实际上构成了事件绑定。
  • 因此,当我点击 button 时,btn.onclick 的执行逻辑如下:
btn.onclick = function() {
    var _this = this; // 取 debounce 执行作用域的 this
    var args = arguments;
    if (timer) {
        clearTimeout(timer);
    }
    //因为闭包的存在 timer 还是取的 debounce 中的 timer
    timer = setTimeout(function () {
        fn.apply(_this, args); // 用 apply 指向调用 debounce 的对象,相当于 _this.fn(args);
    }, delay);
}

此时 this 指向即为 button 元素。以上示例引出了本文的核心主题——函数的 this 指向机制。

调用位置

关于函数的 this,常有一句话总结为 谁调用就指向谁。简而言之,this 的指向与函数的调用位置紧密相关。要确定函数调用时 this 引用了什么,必须明确函数的调用位置。通常需通过函数调用栈来分析真正的调用位置。除了目测代码,还可以借助浏览器的开发者工具(debug 工具)推断目标函数是在哪里被调用的,从而更准确地知晓 this 的指向。例如以下代码:

function foo() {
  console.log('foo')
}
function bar() {
  console.log('bar')
  foo()
}
bar()

若要确定 foo 函数是由谁调用的,可在浏览器中打开调试工具,在 foo 函数第一行设置断点,查看函数调用栈,并找到栈中的第二个元素,该元素即为真正的调用位置。如下图所示:

前言

通过浏览器调试工具可以定位到 foo 函数的真正调用位置。

默认绑定

var a = 2
function foo() {
  var a = 3
  console.log(this.a)
}
foo() // 2

输出结果为 2。因为 foo 函数调用时处于全局环境(即 window)中,查看浏览器中的调用栈:

前言

调用栈中仅包含 foo 函数一个元素,说明调用者即为当前的全局环境 window,因此 this 指向 window。由于最外部的 a 最初是作为 window.a 声明并赋值的,故可理解为 this = window; this.a = 2。值得注意的是,若在 foo 函数内部采用严格模式,则 this 将绑定到 undefined:

var a = 2
function foo() {
  'use strict'
  var a = 3
  console.log(this.a)
}
foo() //`//Cannot read property 'a' of undefined`

隐式绑定

以下列代码为例:

var a = 2
function foo() {
  console.log(this.a)
}
var obj1 = {
  a:3,
  foo: foo
}
obj.foo() //3

输出结果为 3,表明此时 this 指向 obj1,而非全局环境。这里需考虑调用位置是否存在上下文对象,即是否被某个对象拥有或包含。在上述代码中,foo 函数的引用被赋值给 obj1 的 foo 属性 obj1.foo = foo,且在调用 foo 函数时,其前面加上了对 obj1 的引用。当函数引用拥有上下文对象时,隐式绑定规则会将 this 绑定到该上下文对象,即 obj1。理解上下文对象时,无需过于抽象,它仅是一个不确定的代名词,具体指代什么取决于实际语境。

显式绑定

在我看来,默认绑定和隐式绑定是 JavaScript 内置且被动的绑定方式,只要符合这两条规则且无其他规则介入,this 指向便依此确定。显然,这类被动绑定并不完全符合实际编码需求,例如需要指定函数 this 时该如何处理?此时需使用显式绑定,call 和 apply 方法在此发挥作用。参考如下代码:

function foo() {
  console.log(this.a)
}
var obj1 = {
  a: 2
}
var a = 3
foo.call(obj1) // 2

输出结果为 2。原因是 call 方法改变了 foo 函数运行时的 this 指向,将原本指向 window 全局的 this 转为指向 obj1,故输出 2。这也表明显式绑定的优先级高于默认绑定。

new 绑定

首先需明确,JavaScript 中的 new 与其他面向类的语言不同,new 后面的仅仅是一个普通函数,仅被 new 操作符调用而已。使用 new 调用函数时,会执行以下步骤:

  • 创建(或构造)一个全新的对象。
  • 该新对象会执行 [[Prototype]] 连接。
  • 该新对象会绑定到函数调用的 this。
  • 若函数未返回其他对象,则 new 表达式中的函数调用会自动返回该新对象。代码如下所示:
function foo(a) {
  this.a = a
}
var bar = new foo(2)
console.log(bar.a) // 2

输出结果为 2。

不适用的情况

ES6 引入了一种特殊函数:箭头函数。上述四种规则在箭头函数中均不适用,箭头函数的 this 由外层函数或全局链决定。这实际上是对 ES6 之前较为复杂的 this 绑定规则的优化与统一,使实际编码中更易理解。当然,箭头函数也有其局限性,此处不再展开。

总结

在撰写此总结文章前,我对 JavaScript 中 this 问题的理解尚不深入,翻阅几遍《你不知道的 JavaScript》后才有所领悟。本文并未详尽展开 this 绑定时的绑定丢失问题,也未完整列出绑定规则的优先级,暂留此坑,后续再行补充!

参考文献

以上就是 JavaScript this 指向绑定方式及不适用情况详解的详细内容,更多关于 JavaScript this 指向的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章