引言:为什么 this 总是让人困惑?
你是否曾经遇到过这样的问题:
var obj = {
name: '极客时间',
showName: function() {
console.log(this.name);
}
};
var fn = obj.showName;
fn(); // 输出什么?undefined?
明明 fn 是从 obj 上拿下来的,为什么 this.name 却不是 '极客时间'?
这正是 this 的“魔性”所在。很多人觉得 this 是“动态绑定”,但其实它的行为背后有一套清晰的规则和设计哲学。
今天我们就来系统地拆解 this 的本质,结合多个真实案例,让你真正掌握它!
一、this 是谁?它是如何决定指向的?
核心结论:
this不是由函数定义的位置决定的,而是由函数被调用的方式(执行上下文)决定的!
换句话说:谁调用我,我就指向谁。
但这不是简单的“对象方法就指向对象”,我们需要分情况讨论。
二、this 指向的五种经典场景
场景1:作为对象的方法被调用 → 指向该对象
let myObj = {
name: '极客时间',
showThis: function() {
console.log(this); // 输出整个 myObj 对象
this.name = '极客邦';
}
};
myObj.showThis(); // 此时 this 指向 myObj
结果:this === myObj
原理:myObj.showThis() 调用方式表明 showThis 是 myObj 的方法,所以 this 指向 myObj。
场景2:作为普通函数调用 → 指向全局对象(非严格模式),或 undefined(严格模式)
var myObj = {
name: '极客时间',
showThis: function() {
console.log(this);
}
};
var foo = myObj.showThis;
foo(); // 输出 window(浏览器环境)
这里 foo() 是一个普通函数调用,没有明确的调用者,所以 this 指向全局对象(如 window)。
注意:如果使用 'use strict',此时 this 会是 undefined,防止污染全局变量。
'use strict';
var foo = myObj.showThis;
foo(); // 输出 undefined
场景3:通过 call / apply 显式绑定 this
let bar = { myName: '极客邦' };
function foo() {
this.myName = '极客时间';
}
foo.apply(bar); // 或 foo.call(bar)
console.log(bar.myName); // 输出:极客时间
apply 和 call 允许我们手动指定 this 的值,这是实现“借用方法”的关键。
小技巧:apply 接受数组参数,call 接受列表参数。
场景4:构造函数中 this 指向新创建的实例
function CreateObj() {
console.log(this); // 输出一个空对象(新实例)
this.name = '极客时间';
}
var obj = new CreateObj();
console.log(obj.name); // 极客时间
new 操作符做了三件事:
- 创建一个空对象
{}; - 设置其原型链为
CreateObj.prototype; - 执行
CreateObj.call(新对象),即把this绑定到新对象上; - 返回这个对象(除非返回的是另一个对象)。
模拟 new 的过程:
var temObj = {};
CreateObj.call(temObj);
temObj.__proto__ = CreateObj.prototype;
var obj = temObj;
场景5:事件处理函数中的 this
<a href="#" id="link">点击我</a>
<script>
document.getElementById('link').addEventListener('click', function() {
console.log(this); // 输出:<a> 元素本身
});
</script>
在 DOM 事件监听器中,this 默认指向触发事件的元素。
如果你在事件回调中使用了箭头函数,this 就不会指向 DOM 元素,而是继承外层作用域的 this。
三、this 与作用域链的区别
很多人混淆了 this 和变量查找机制(作用域链),下面我们对比一下:
var bar = {
myName: 'time.geekbang.com',
printName: function() {
console.log(myName); // 查找作用域链:先找局部,再向上
console.log(bar.myName); // 显式访问 bar 对象属性
console.log(this); // 当前执行上下文
console.log(this.myName); // 通过 this 访问属性
}
};
var myName = '极客邦'; // 全局变量
var _printName = bar.printName;
_printName(); // 输出:'极客邦'(因为 myName 是全局变量)
bar.printName(); // 输出:'time.geekbang.com'
| 代码 | 查找方式 | 结果 |
|---|---|---|
myName | 作用域链查找 | 找到全局变量 '极客邦' |
bar.myName | 显式访问对象属性 | 'time.geekbang.com' |
this.myName | 依赖 this 指向 | 取决于调用方式 |
关键点:this 是运行时绑定的,而变量查找是编译期确定的(词法作用域)。
四、this 设计背后的“历史包袱”
JavaScript 早期为了支持面向对象编程,引入了 this 来模拟类的行为。但由于当时还没有 class 语法,只能靠函数 + this 实现 OOP。
设计缺陷:
this的行为不一致,容易造成误解;- 普通函数调用时
this默认指向全局对象,导致全局污染; var声明的变量会被挂载到window上,let/const才避免这个问题。
改进措施:
- 使用
'use strict'防止意外的this指向; - 使用箭头函数(
=>)避免this绑定问题; - 使用
bind、call、apply显式控制this; - ES6+ 推出
class,让 OOP 更清晰。
五、总结:一张图记住 this
| 调用方式 | this 指向 |
|---|---|
对象方法调用 obj.method() | obj |
普通函数调用 func() | 全局对象(非严格)或 undefined(严格) |
func.call(obj) / func.apply(obj) | obj |
new func() | 新创建的对象 |
| DOM 事件监听器 | 触发事件的元素 |
写在最后
this 是 JavaScript 中最具争议但也最重要的概念之一。它看似简单,实则蕴含着语言设计的深意。掌握它,不仅能帮你写出更稳定的代码,还能更好地理解 JS 的执行机制。