🔥 一文彻底搞懂 JavaScript 中的 this:从困惑到精通

75 阅读4分钟

🔥 一文彻底搞懂 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 会自动:

    1. 创建空对象
    2. 绑定 this 到该对象
    3. 执行函数体
    4. 返回对象

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 常被吐槽,原因有三:

  1. 历史包袱:早期没有类,靠函数模拟 OOP,this 成了“万能胶水”
  2. 默认指向 window:导致全局污染(var a = 1 → window.a = 1
  3. 动态绑定 vs 静态作用域:与 JS 其他部分的设计哲学冲突

但别急着骂!这种设计也有优点:

  • 方法可以被多个对象复用(通用工具函数)
  • 支持强大的“方法借用”(如 Array.prototype.slice.call(arguments)

🌈 ES6 的箭头函数正是为解决 this 困惑而生:
箭头函数没有自己的 this,它继承外层作用域的 this


✅ 终极总结:一张表搞定 this

调用方式this 指向示例
普通调用 fn()全局对象(严格模式:undefinedfoo()
对象方法 obj.fn()objuser.getName()
显式绑定 fn.call(obj)objgreet.call(person)
构造函数 new Fn()新创建的实例new Date()
事件回调触发事件的 DOM 元素button.onclick = fn

牢记三句话:

  1. 变量查找看“出生地”,this 查看“谁叫你”
  2. let/const 不挂 windowvar 会——少用 var
  3. 开启 'use strict',让错误无处藏身

🎯 写在最后

理解 this,是 JavaScript 进阶的分水岭。
它看似随意,实则有章可循;看似混乱,实则逻辑自洽。

当你下次再看到 this,别慌——
先问一句:“它是怎么被调用的?”
答案自然浮现。

掌握 this,你离 JS 高手,又近了一步!🚀