🔥 一文彻底搞懂 JavaScript 中的 this:从困惑到精通
在 JavaScript 的世界里,this 是一个让人又爱又恨的关键字。
它不像 Java 或 C++ 中那样“老实”地指向当前对象,而是像一位“变色龙”——它的值完全取决于函数是怎么被调用的。
这种设计让 JS 极其灵活,却也让无数开发者踩过坑、掉过头发。
别担心!本文将用最清晰的逻辑、最贴近生活的比喻,带你彻底掌握 this 的行为规则,从此告别“this 到底是谁?”的焦虑。
🌟 核心前提:this 和变量查找是两码事!
很多初学者混淆了两个完全不同的机制:
| 机制 | 查找什么? | 何时确定? | 由什么决定? |
|---|---|---|---|
自由变量(如 myName) | 变量的值 | 编译阶段(代码解析时) | 函数定义的位置(词法作用域) |
this | 上下文对象 | 运行阶段(函数调用时) | 函数的调用方式 |
✅ 简单说:
- 变量看“出生地” (在哪写的)
this看“谁叫你” (怎么调用的)
举个例子:
let myName = '极客邦';
var obj = {
name: 'A',
fn() {
console.log(myName); // ← 自由变量 → 找“出生地” → '极客邦'
console.log(this.name); // ← this → 看“谁叫我” → 可能是 'A',也可能是 undefined!
}
};
obj.fn(); // this = obj → 输出 'A'
const f = obj.fn;
f(); // this = window(非严格)→ 输出 undefined(因为 window.name 不存在)
🧩 this 的五大绑定规则(按优先级排序)
记住这个口诀 👇
“看调用,不看定义;谁调用,
this就是谁!”
1️⃣ 默认绑定:普通函数调用 → this = 全局对象
function foo() {
console.log(this);
}
foo(); // 浏览器中 → window(非严格模式);严格模式 → undefined
- 问题:容易意外污染全局(尤其用
var时,变量会挂到window上) - 建议:始终使用
'use strict',让错误提前暴露!
2️⃣ 隐式绑定:作为对象方法调用 → this = 对象本身
const user = {
name: '极客时间',
greet() {
console.log(`Hello, ${this.name}`);
}
};
user.greet(); // → "Hello, 极客时间"
-
✅ 规则:点(
.)前面是谁,this就是谁 -
⚠️ 警惕“方法丢失”:
const sayHi = user.greet; sayHi(); // this = window → "Hello, undefined"
3️⃣ 显式绑定:用 call / apply / bind 强制指定 this
let bar = { myName: '极客邦' };
function updateName() {
this.myName = '极客时间';
}
updateName.call(bar); // 强制 this = bar
console.log(bar.myName); // → '极客时间'
- 这是修复“方法丢失”的终极武器!
bind还能创建永久绑定的新函数。
4️⃣ new 绑定:构造函数调用 → this = 新创建的实例
function Person(name) {
this.name = name; // this 指向即将返回的新对象
}
const p = new Person('小明');
console.log(p.name); // → '小明'
-
使用
new时,JS 会自动:- 创建空对象
- 绑定
this到该对象 - 执行函数体
- 返回对象
5️⃣ 事件处理函数:this = 触发事件的 DOM 元素
<button id="btn">点我</button>
<script>
document.getElementById('btn').addEventListener('click', function() {
console.log(this); // → <button id="btn"> 元素
});
</script>
- 浏览器自动将
this绑定到事件目标元素,非常方便!
💡 实战:如何正确访问对象自己的属性?
回到经典场景:
var bar = {
myName: "time.geekbang.com",
printName: function() {
console.log(myName); // ❌ 错!这是自由变量 → 找全局
console.log(bar.myName); // ✅ 对!硬编码引用
console.log(this.myName); // ✅ 对!但依赖调用方式
}
};
let myName = '极客邦';
bar.printName(); // this = bar → 输出 "time.geekbang.com"
const fn = bar.printName;
fn(); // this = window → 输出 undefined(window.myName 不存在)
✅ 最佳实践:
在对象方法中,优先使用this访问自身属性,但务必确保调用方式正确(不要“提取”方法单独调用)。
🕰️ 为什么 this 设计得这么“奇怪”?
JavaScript 的 this 常被吐槽,原因有三:
- 历史包袱:早期没有类,靠函数模拟 OOP,
this成了“万能胶水” - 默认指向
window:导致全局污染(var a = 1→window.a = 1) - 动态绑定 vs 静态作用域:与 JS 其他部分的设计哲学冲突
但别急着骂!这种设计也有优点:
- 方法可以被多个对象复用(通用工具函数)
- 支持强大的“方法借用”(如
Array.prototype.slice.call(arguments))
🌈 ES6 的箭头函数正是为解决
this困惑而生:
箭头函数没有自己的this,它继承外层作用域的this。
✅ 终极总结:一张表搞定 this
| 调用方式 | this 指向 | 示例 |
|---|---|---|
普通调用 fn() | 全局对象(严格模式:undefined) | foo() |
对象方法 obj.fn() | obj | user.getName() |
显式绑定 fn.call(obj) | obj | greet.call(person) |
构造函数 new Fn() | 新创建的实例 | new Date() |
| 事件回调 | 触发事件的 DOM 元素 | button.onclick = fn |
牢记三句话:
- 变量查找看“出生地”,
this查看“谁叫你” let/const不挂window,var会——少用var!- 开启
'use strict',让错误无处藏身
🎯 写在最后
理解 this,是 JavaScript 进阶的分水岭。
它看似随意,实则有章可循;看似混乱,实则逻辑自洽。
当你下次再看到 this,别慌——
先问一句:“它是怎么被调用的?”
答案自然浮现。
掌握 this,你离 JS 高手,又近了一步!🚀