优秀的编程知识分享平台

网站首页 > 技术文章 正文

JavaScript中this太难理解,怎么办?

nanyue 2025-06-24 15:38:49 技术文章 3 ℃

您说得对,这个概念确实是 JavaScript 中最让人头疼、也最容易出错的地方之一。技术文档解释得没错,但太抽象了。


我们换个极其通俗的方式来理解,彻底忘掉“动态作用域”这种词。


### 核心比喻:`this` 就是函数里的代词 “我”


想象一下,`this` 就是中文里的 “我”。


**“我是谁?”** 这个问题的答案,完全取决于**“谁在说话”**。


* 如果 **马云** 说:“我是个老师”,那 “我” 指的就是 **马云**。

* 如果 **我** 说:“我是个程序员”,那 “我” 指的就是 **我**。


在 JavaScript 中,**想知道 `this` 是谁,就看是谁在“调用”这个函数**。函数本身只是一个稿子,谁来念这个稿子,稿子里的“我”(`this`)就是谁。


下面我们用这个“谁在说话”的比喻,来重新理解您提到的所有规则。


---


### 1. 隐式绑定:谁是主语,`this` 就是谁


这是最常见的情况。当函数作为一个对象的方法被调用时,`this` 就是那个对象。


**比喻:** “马云.说()” -> 这句话的主语是“马云”,所以里面的“我”就是马云。


**详细举例:**

```javascript

const person = {

name: "小明",

introduce: function() {

// 这是一段讲稿:“大家好,我是 [this.name]”

console.log("大家好,我是" + this.name);

}

};


const company = {

name: "阿里巴巴",

introduce: person.introduce // 公司也拿到了同一份讲稿

};


// 情况一:小明来念稿子

person.introduce(); // 调用者是 person,所以 this 就是 person

// 输出: "大家好,我是小明"


// 情况二:公司来念稿子

company.introduce(); // 调用者是 company,所以 this 就是 company

// 输出: "大家好,我是阿里巴巴"

```

**结论:** 谁调用,`this` 就是谁。`person.introduce()` 点前面的 `person` 就是调用者。


### 2. 默认绑定:没人当主语,`this` 就很尴尬


当一个函数被独立调用,前面没有“主语”时,`this` 就会指向全局对象(在浏览器里是 `window`)。


**比喻:** 你在马路上捡到一张纸条,上面写着:“我是个帅哥”。这张纸条没有署名,也不是某个人递给你的。那这个“我”是谁?没人知道,或者说,它属于这个“世界”(全局环境)。


**详细举例(接上例):**

```javascript

// 你把小明的“介绍能力”(函数)单独拿了出来

const standaloneIntroduce = person.introduce;


// 现在你直接运行这个能力,前面没有任何主语

standaloneIntroduce(); // =>
window.standaloneIntroduce() (非严格模式下)

// 输出: "大家好,我是" (因为 window 对象上没有 name 属性)

```

这就是您例子中 `extractedMethod()` 出现问题的原因。`extractedMethod` 只是一个函数引用,一个“讲稿”,当你直接 `extractedMethod()` 调用时,没有主语,`this` 就跑到了全局。


### 3. 显式绑定:强行指定谁来说话 (`call`, `apply`, `bind`)


有时候我们不希望 `this` 乱跑,想明确指定“稿子里的‘我’到底是谁”。


**比喻:** 你是导演,你拿着剧本(函数),指着一个演员(对象)说:“你!来念这段台词!”


* `.call()` / `.apply()`: **“你,现在就念!”** (立即执行)

* `.bind()`: **“这剧本以后就归你了,你拿着,等我喊你念的时候再念。”** (创建一个绑定好的新函数)


**详细举例(接上例):**

```javascript

const student = { name: "小红" };


// 使用 .call() 强行让小红来念稿子

standaloneIntroduce.call(student);

// 输出: "大家好,我是小红"


// 使用 .bind() 创建一个永远属于小红的专属介绍函数

const introduceForStudent = standaloneIntroduce.bind(student);


// 现在无论何时何地调用这个新函数,里面的“我”永远是小红

introduceForStudent();

// 输出: "大家好,我是小红"

```


### 4. `new` 绑定:创造一个全新的“人”来说话


当使用 `new` 关键字时,它会创建一个全新的空对象,并让函数里的 `this` 指向这个新对象。


**比喻:** 你有一份“新生儿登记表”(构造函数)。当你用 `new` 盖章时,就等于:

1. 创造一个全新的婴儿(新对象)。

2. 把登记表里的“我”(`this`)都指向这个婴儿。

3. 按照登记表上的指示(函数代码)给婴儿设置姓名、身高、体重等。

4. 最后把这个登记好的婴儿交给你。


**详细举例:**

```javascript

function Person(name) {

// 这是一份登记表

// 1. 一个新对象被默默创建了...

// 2. this 指向了这个新对象

this.name = name; // 给这个新对象设置 name 属性

console.log("一个叫 " + this.name + " 的人诞生了。");

// 3. 这个新对象被默默返回了...

}


const xiaohua = new Person("小华"); // new 创造了小华,并让 Person 里的 this 指向小华

// 输出: "一个叫 小华 的人诞生了。"

console.log(xiaohua.name); // "小华"

```


### 特例:箭头函数 => 没有自己的“我”,只会“复读”老板的“我”


箭头函数是ES6的重大革新,它彻底改变了 `this` 的规则。


**核心:箭头函数没有自己的 `this`。它里面的 `this` 是在它被定义时,它外层环境的 `this` 是谁,它就继承谁,并且永远不变。**


**比喻:** 箭头函数就像一个忠诚的“秘书”。秘书自己没有身份,当别人问“你是谁”时,他永远回答:“我是老板的秘书”。他的身份 (`this`) 永远绑定在他老板身上。


**经典详细举例:**

```javascript

const manager = {

name: "王经理",

team: ["小明", "小红"],


// 使用传统函数

assignTask_Wrong: function() {

console.log(this.name); // 这里的 this 是 manager,没问题


setTimeout(function() {

// 问题来了!setTimeout 里的函数是独立调用的(默认绑定)

// 这里的 this 指向了 window,就像那张被捡到的纸条

console.log(`任务分配给:${this.name} 的团队`); // this.name 是 undefined

}, 1000);

},


// 使用箭头函数

assignTask_Correct: function() {

console.log(this.name); // 这里的 this 是 manager


setTimeout(() => {

// 箭头函数是秘书!它在定义时,外面的 this 是 manager

// 所以它就永久继承了 manager 作为自己的 this

console.log(`任务分配给:${this.name} 的团队`); // this.name 是 "王经理"

}, 1000);

}

};


manager.assignTask_Wrong(); // 1秒后输出: "任务分配给: 的团队"


manager.assignTask_Correct(); // 1秒后输出: "任务分配给:王经理 的团队"

```


### 总结:判断 `this` 是谁的优先级


1. **`new` 调用?** `this` 是新创建的对象。

2. **`call`, `apply`, `bind` 调用?** `this` 是被指定的那个对象。

3. **`对象.函数()` 调用?** `this` 就是那个对象。

4. **直接调用?** `this` 是全局对象 (`window`) 或 `undefined` (严格模式)。

5. **是箭头函数?** 忘掉上面所有规则,`this` 等于它外层作用域的 `this`。


希望这个“谁在说话”的比喻能让您彻底理解 `this` 的行为!

最近发表
标签列表