🧠 深入理解 JavaScript 中的 this:从自由变量到绑定规则
"
this是 JavaScript 最容易被误解的概念之一 ------ 它不是由函数定义决定的,而是由调用方式决定的。"
在日常开发中,我们经常遇到这样的困惑:
- 为什么同一个函数,有时
this指向对象,有时却指向window? - 为什么在事件回调里
this是 DOM 元素,而赋值后调用就变成全局对象? - 严格模式下
this为什么会变成undefined?
本文将结合你可能写过的代码,系统梳理 this 的设计逻辑、绑定规则与常见陷阱,并告诉你:如何真正掌控 this。
🔍 一、this 不是"自由变量"------它和作用域无关!
很多初学者会混淆 变量查找 和 this 绑定,认为它们是一回事。其实:
| 特性 | 自由变量(如 myName) |
this |
|---|---|---|
| 查找时机 | 编译阶段(词法作用域) | 执行阶段(动态绑定) |
| 查找依据 | 函数定义位置(Lexical Scope) | 函数调用方式 |
| 是否受作用域链影响 | ✅ 是 | ❌ 否 |
来看一段典型代码:
Js
'use strict'; // 严格模式
var myName = '极客邦'; // 挂载到 window
var bar = {
myName: 'time.geekbang.com',
printName: function () {
console.log(myName); // ✅ 自由变量 → '极客邦'(全局)
console.log(this.myName); // ❓ this 取决于怎么调用!
}
};
function foo() {
let myName = '极客时间';
return bar.printName;
}
var _printName = foo();
_printName(); // this → undefined(严格模式)
bar.printName(); // this → bar
myName是自由变量,按词法作用域 查找 → 总是取全局的'极客邦'this.myName的值完全取决于函数如何被调用
💡 关键结论:
this和作用域链毫无关系!它是运行时的"调用上下文"决定的。
🎯 二、this 的五种绑定规则(优先级从高到低)
1️⃣ 显式绑定(Explicit Binding)
使用 call / apply / bind 强制指定 this
Js
function foo() {
this.myName = '极客时间';
}
let bar = { name: '极客邦' };
foo.call(bar); // this → bar
console.log(bar); // { name: '极客邦', myName: '极客时间' }
✅ 最高优先级,直接覆盖其他规则。
2️⃣ 隐式绑定(Implicit Binding)
作为对象的方法 调用 → this 指向该对象
Js
var myObj = {
name: '极客时间',
showThis: function() {
console.log(this); // → myObj
}
};
myObj.showThis(); // 隐式绑定
⚠️ 陷阱:隐式丢失(Implicit Loss)
Js
var foo = myObj.showThis; // 函数引用被赋值
foo(); // 普通函数调用 → this = window(非严格)或 undefined(严格)
这就是为什么
setTimeout(myObj.method, 1000)会丢失this!
3️⃣ 构造函数绑定(new Binding)
使用 new 调用函数 → this 指向新创建的实例
Js
function CreateObj() {
this.name = '极客时间';
}
var obj = new CreateObj(); // this → obj
内部机制相当于:
Js
var temObj = {};
temObj.__proto__ = CreateObj.prototype;
CreateObj.call(temObj);
return temObj;
4️⃣ 普通函数调用(Default Binding)
既不是方法,也没用 new 或 call → 默认绑定
- 非严格模式 :
this → window(浏览器)或global(Node) - 严格模式 :
this → undefined
Js
function foo() {
console.log(this); // 非严格 → window;严格 → undefined
}
foo();
🚫 这是 JS 的一个"历史包袱":作者 Brendan Eich 当年为了快速实现,让普通函数的
this默认指向全局对象,导致大量意外污染。
5️⃣ 箭头函数(Arrow Function)
没有自己的 this !继承外层作用域的 this
Js
class Button {
constructor() {
this.text = '点击';
document.getElementById('btn').addEventListener('click', () => {
console.log(this.text); // ✅ 正确指向 Button 实例
});
}
}
✅ 箭头函数是解决"回调中
this丢失"的利器,但不能用于需要动态this的场景(如构造函数、对象方法)。
🌐 三、特殊场景:DOM 事件中的 this
Html
<a href="#" id="link">点击我</a>
<script>
document.getElementById('link').addEventListener('click', function() {
console.log(this); // → <a id="link"> 元素
});
</script>
这是 addEventListener 的规范行为:
回调函数中的
this自动绑定为注册事件的 DOM 元素。
但注意:
- 如果用箭头函数 →
this不再是元素! - 如果把函数赋值给变量再调用 → 隐式丢失!
⚠️ 四、为什么 this 的设计被认为是"不好"的?
- 违反直觉 :函数定义时看不出
this指向谁。 - 容易出错:隐式丢失、全局污染频发。
- 依赖调用方式 :同一函数,不同调用,
this不同。
正因如此,ES6 引入了
class和箭头函数,弱化对this的依赖。
✅ 五、最佳实践建议
| 场景 | 推荐做法 |
|---|---|
| 对象方法 | 用普通函数,避免赋值导致隐式丢失 |
| 回调函数(如事件、定时器) | 用箭头函数,或 .bind(this) |
| 构造函数 | 用 class 替代传统函数 |
| 需要强制指定上下文 | 用 call / apply / bind |
| 避免全局污染 | 使用严格模式 + let/const |
🔚 结语
this 并不神秘,它只是 JavaScript 动态绑定机制的一部分。理解它的核心在于:
"谁调用了这个函数,
this就是谁。"
掌握五种绑定规则,避开隐式丢失陷阱,你就能在任何场景下准确预测 this 的指向。
最后记住:现代 JavaScript 已经提供了更安全的替代方案(如 class、箭头函数),不必死磕 this ------ 但你必须懂它。
📚 延伸阅读
- 《你不知道的JavaScript(上卷)》------ "this & 对象原型"章节
- MDN: this
- ECMAScript 规范:Function Calls
欢迎在评论区分享你踩过的 this 坑! 👇
如果觉得有帮助,别忘了点赞 + 关注~ ❤️
标签:#JavaScript #this #前端 #作用域 #掘金