this 指向全解:五条绑定规则、优先级与箭头函数的例外
## 一、核心认知:this 是调用时决定的
this 不取决于函数写在哪,取决于**函数怎么被调用**。同一个函数,赋值给不同对象再调用,this 完全不同:
```js
function who() {
return this.name;
}
const a = { name: 'A', who };
const b = { name: 'B', who };
who(); // undefined(严格模式)或 window.name(非严格)
a.who(); // 'A'
b.who(); // 'B'
```
判断 this 只需要问一个问题:**这个函数最终是谁调的?**
## 二、五条绑定规则(按优先级从高到低)
### 1. new 绑定:this 指向新创建的对象
```js
function Person(name) {
this.name = name;
}
const p = new Person('Tom');
p.name; // 'Tom'
```
new 的执行过程:创建空对象 → 原型挂到对象上 → this 指向该对象执行构造函数 → 返回该对象。
### 2. 显式绑定:call / apply / bind
```js
fn.call(obj, 1, 2); // 参数逐个传
fn.apply(obj, [1, 2]); // 参数数组传
const bound = fn.bind(obj, 1); // 永久绑定,返回新函数
```
注意 bind 的绑定是**不可覆盖**的(除了 new),且对原始值参数会自动装箱:`fn.call(5)` 里 this 是 `Number {5}`。
### 3. 隐式绑定:this 指向最后调用它的对象
```js
const obj = {
name: 'obj',
greet() {
return this.name;
},
};
obj.greet(); // 'obj'
```
注意"最后"二字——链式调用 `a.b.c()` 时,this 是 `c` 所在的 `a.b`,不是 `a`。
### 4. 默认绑定:没人调就指向全局
```js
function plain() {
return this;
}
plain(); // window(浏览器)/ globalThis(Node)/ undefined(严格模式)
```
### 5. 箭头函数:沿用外层 this
箭头函数没有自己的 this,定义时所在的词法作用域是谁,this 就是谁,且 call/apply/bind 改不动:
```js
const obj = {
name: 'obj',
normal() {
return this.name; // 'obj'
},
arrow: () => this.name, // 外层作用域的 this,通常是 undefined
};
```
## 三、隐式绑定丢失:最高频的 bug
最常见的翻车是把方法当回调传出去,调用者变成了别的对象:
```js
const button = {
label: '提交',
handleClick() {
console.log(this.label); // 期望 '提交'
},
};
setTimeout(button.handleClick, 100); // 输出 undefined!
```
`button.handleClick` 取出的是函数本身,setTimeout 调用时 this 是 window/undefined。三种修法:包一层箭头 `() => button.handleClick()`、`bind(button)`、或直接用箭头函数定义方法。
## 四、手写 call / apply / bind
面试常考,核心思路都是"借对象的属性临时挂载再调用":
```js
Function.prototype.myCall = function (context, ...args) {
context = context || globalThis;
const key = Symbol('fn'); // 防止覆盖原有属性
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
Function.prototype.myBind = function (context, ...args) {
const fn = this;
return function bound(...newArgs) {
// new 调用时 this 指向实例,绑定失效
if (this instanceof bound) {
return new fn(...args, ...newArgs);
}
return fn.apply(context, args.concat(newArgs));
};
};
```
两个细节:用 Symbol 做临时 key 避免污染;bind 版本必须处理 new 覆盖绑定的情况。
## 五、实践中的三条军规
1. **React class 组件的方法必须 bind**:`this.handleClick = this.handleClick.bind(this)`,或者干脆用 class property 箭头函数 `handleClick = () => {}`。
2. **Vue 的 methods 不用管**:编译时已自动绑定到组件实例,直接写普通方法即可。
3. **回调优先用箭头函数**:数组方法(map/filter/forEach)的回调写箭头,this 自动沿外层,省掉一层 bind。
## 六、总结
判断 this 的顺序:看是否被 new → 是否被 call/apply/bind → 是否被对象调用 → 都没有就是默认绑定;箭头函数全程例外,直接看外层作用域。把这个顺序背熟,90% 的 this 问题都能当场定位。
- 点赞
- 收藏
- 关注作者
评论(0)