this 是 JavaScript 中最让人困惑也最强大的关键字之一。它的行为与许多传统语言(如 Java、C++)中的 this 或 self 完全不同——this 不是在定义时决定的,而是在函数被调用的那一刻才确定的。要真正理解 this,不能只看函数本身的代码,而要看函数的调用方式。
5.1.1 this 到底是什么——执行上下文的属性
在 JavaScript 引擎内部,每当一个函数被调用时,都会创建一个新的执行上下文(Execution Context)。这个上下文对象包含了函数执行所需的所有信息:变量对象、作用域链和 this 值。this 就是这个上下文对象中的一个属性,用于指向“函数在哪里被调用时所关联的对象”。
这意味着,同一个函数,以不同的方式调用,其 this 指向可能完全不同——this 的值是动态绑定的,完全取决于调用点的调用形式。
5.1.2 动态绑定的核心逻辑
可以用一句更直白的话来概括:
在绝大多数情况下,
this指向调用这个函数的对象。如果没有明确的调用对象,则根据严格模式的不同,指向全局对象或undefined。
但“调用这个函数的对象”并不总是直观的。为此,JavaScript 社区总结出了一套清晰的判定规则——四大绑定规则。本节先把this的本质定调为“动态绑定于执行上下文的属性”,然后引出即将在第5.2节展开的绑定规则。
5.1.3 简单示例:不同调用方式导致不同的 this
看下面的代码,你就能直观感受到this的动态性:
function showThis() {
console.log(this);
}
// 1. 直接调用
showThis(); // 非严格模式下输出 window(或 global),严格模式下输出 undefined
// 2. 作为对象的方法调用
const obj = { name: 'Alice', showThis: showThis };
obj.showThis(); // 输出 obj 对象 { name: 'Alice', showThis: f }
// 3. 使用 call/apply 显示绑定
const anotherObj = { name: 'Bob' };
showThis.call(anotherObj); // 输出 { name: 'Bob' }
// 4. 使用 new 调用
const instance = new showThis(); // 输出一个 showThis 的实例对象
同一个 showThis 函数,在不同调用方式下,this 分别指向了完全不同的对象:全局对象、obj、anotherObj、新创建的实例。这正是动态绑定的生动体现。
5.1.4 为什么 JavaScript 要这样设计 this?
this 的动态绑定特性源自 JavaScript 的设计场景——它最初是用来处理 Web 页面的用户交互的。在事件处理中,同一个函数可能被绑定到不同的 DOM 元素上,this 能自动指向触发事件的元素,避免了手动传递对象的麻烦:
// 假设页面上有多个按钮
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
function handleClick() {
console.log(this.id); // 动态指向被点击的按钮
}
document.getElementById('btn1').onclick = handleClick;
document.getElementById('btn2').onclick = handleClick;
// 点击 btn1 输出 'btn1',点击 btn2 输出 'btn2'
</script>
如果 this 是静态绑定的(像 Python 的 self 那样),那么想实现这种自动指向的便捷性,就需要额外传入参数或创建多个包装函数。动态绑定赋予了 JavaScript 极高的灵活性。
5.1.5 两个常见误区
误区一:“this 指向函数自身”
很多人认为函数内部的 this 指向函数本身,这在其他语言中或许是成立的,但在 JavaScript 中并不正确。如果你需要在函数内部引用函数本身,应该使用函数的名称或者 arguments.callee(严格模式禁用)。
function foo() {
console.log(this === foo); // false (非严格模式指向全局对象)
console.log(foo); // 正确引用函数本身
}
foo();
误区二:“this 指向函数的作用域”
this 和作用域链是两套完全独立的系统。this 指向的是对象,而不是变量对象或词法作用域。你无法通过 this 来访问函数内部的局部变量。
function bar() {
var a = 10;
console.log(this.a); // 不是10,试图访问的是调用对象上的 a 属性,通常为 undefined
}
bar();
5.1.6 箭头函数:例外中的例外
这里需要提前提及:箭头函数没有自己的 this,它的 this 是通过词法作用域从外层代码中继承的,而不是动态绑定。这意味着箭头函数的 this 在定义时就确定了,与调用方式无关。这也是箭头函数被广泛用于需要保持外层 this 的场景(如回调函数)的根本原因。具体细节将在第5.4节深入展开。
5.1.7 总结:this 判定的思考模型
在继续学习下一节的具体绑定规则之前,请牢牢记住一个心理模型:
- 找到函数的调用点(call-site),即函数被调用的地方。
- 分析调用形式:是作为普通函数调用?作为对象的方法?还是用了
call/apply/bind?或者前有new? - 根据规则(默认绑定、隐式绑定、显式绑定、new 绑定)逐一判定优先级。
这四点将在下一节详细拆解,而本节的核心是让你吃透动态绑定的本质:this 不是函数的一部分,而是执行上下文的一部分;它取决于函数怎么被调用,而不是函数怎么写。理解了这一点,你就已经把握住了 this 机制的灵魂。