本文详细解析 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() {
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 指向的资料请关注本站其它相关文章!