this 是谁?—— JavaScript 中最“渣”的关键字,你真的懂它吗?

46 阅读4分钟

“this 不是你以为的 this,this 是 JavaScript 给你挖的坑。”
—— 一位被 this 虐哭过的前端工程师


开场白:this 的“人格分裂”现场

在 JavaScript 的世界里,有一个关键字堪称“情感大师”——它时而深情款款指向全局对象,时而冷酷无情绑定到某个函数,甚至还能在箭头函数里直接“消失”。没错,它就是 this

很多初学者(甚至老手)一看到 this 就头皮发麻。为什么?因为它不讲武德!它的值不是定义时决定的,而是运行时调用方式决定的。今天我们就来揭开 this 的神秘面纱,顺便聊聊怎么把它“驯服”。


一、this 的四大基本法则(别再背错了!)

1️⃣ 默认绑定:裸奔的 this → 指向全局(或 undefined)

function sayHi() {
  console.log(this); // 浏览器中是 window;严格模式下是 undefined
}
sayHi(); // 裸调用!this 无依无靠

📌 记住:谁调用?没人调!那 this 就去流浪(全局)或者自闭(严格模式)。


2️⃣ 隐式绑定:对象.方法 → this 指向对象

const user = {
  name: "Cherry",
  greet() {
    console.log(this.name); // "Cherry"
  }
};
user.greet(); // 谁调用?user!所以 this === user

✅ 这是最常见也最“靠谱”的情况。但小心“隐式丢失”!

const fn = user.greet;
fn(); // this 变成 window / undefined!因为现在是裸调用了

3️⃣ 显式绑定:call / apply / bind → 手动指定 this

user.greet.call({ name: "Alice" }); // "Alice"
const boundGreet = user.greet.bind({ name: "Bob" });
boundGreet(); // "Bob"
  • call / apply:立即执行,指定 this
  • bind:返回一个“婚约函数”,this 被永久锁定(像签了忠诚协议)

💍 bind 就像给函数办了结婚证:this 此生只属于你,永不背叛。


4️⃣ new 绑定:构造函数 → this 指向新对象

function Person(name) {
  this.name = name;
}
const p = new Person("David");
console.log(p.name); // "David"

🧬 new 会创建一个全新对象,并把 this 绑定到它身上。这是唯一能“造人”的方式(误)。


二、定时器里的 this 陷阱:setTimeout 的“背叛”

来看一段经典代码:

const a = {
  name: "Cherry",
  func2() {
    setTimeout(function() {
      console.log(this.name); // ❌ undefined!this 指向 window
    }, 1000);
  }
};
a.func2();

为什么?因为 setTimeout 里的回调函数是裸调用的!this 跟 a 没半毛钱关系。

解法三连击:

✅ 方法一:that = this(传统艺能)

func2() {
  const that = this;
  setTimeout(function() {
    console.log(that.name); // "Cherry"
  }, 1000);
}

👴 老派但可靠,像你爸的旧皮带——土,但管用。

✅ 方法二:bind 强制绑定

setTimeout(function() {
  console.log(this.name);
}.bind(this), 1000);

🔒 绑得死死的,this 别想跑。

✅ 方法三:箭头函数(现代解法)

func2() {
  setTimeout(() => {
    console.log(this.name); // "Cherry"!
  }, 1000);
}

🎯 箭头函数没有自己的 this,它会继承外层作用域的 this
所以在 func2 里,this 是 a,箭头函数直接“蹭”过来用!


三、箭头函数:this 的“佛系青年”

“我不争不抢,没有 this,全靠继承。”

箭头函数:

  • 没有 this
  • 没有 arguments
  • 不能当构造函数(new 会报错)
  • 没有 prototype
const arrow = () => {
  console.log(this); // 外层作用域的 this(通常是 window 或模块上下文)
};
arrow(); // 输出 window(非严格模式)

⚠️ 所以别在对象方法里用箭头函数!否则 this 会“飘”走:

const bad = {
  name: "Oops",
  getName: () => {
    console.log(this.name); // ❌ undefined!this 不是 bad
  }
};
bad.getName(); // this 指向全局,name 不存在

四、面试高频题:你能答对几个?

Q1:以下代码输出什么?

var name = "windowName";
const obj = {
  name: "Cherry",
  getName: function() {
    return this.name;
  }
};
const fn = obj.getName;
console.log(fn());

✅ 答案:"windowName"(非严格模式)或 undefined(严格模式)
👉 因为 fn() 是裸调用!


Q2:如何让下面的代码输出 "Cherry"?

setTimeout(obj.getName, 1000);

✅ 答案(任选其一):

  • setTimeout(obj.getName.bind(obj), 1000)
  • setTimeout(() => obj.getName(), 1000)
  • setTimeout(function() { obj.getName(); }, 1000)(注意这里没用 this)

五、总结:this 的“驯服指南”

场景this 指向应对策略
普通函数调用全局 / undefined避免裸调用
对象方法调用该对象✅ 安全
回调函数(如 setTimeout)全局用箭头函数 / bind / that
箭头函数外层作用域别在对象方法里乱用
构造函数新实例✅ 正常使用

结语:this 不渣,只是你没读懂它

this 并不是 JavaScript 的 bug,而是一种动态上下文机制。它灵活,但也危险。理解它的规则,就像学会和一个性格多变的朋友相处——你知道他什么时候会“翻脸”,也知道怎么让他“听话”。

下次面试官问:“说说 this 的指向?”
你可以微微一笑:“this 啊,它从来不说‘我是谁’,只看‘谁叫我’。”


本文已通过“人类可读性测试”✅,保证不催眠,只催更。