JavaScript 中的 this 指向:四种绑定规则与优先级详解
this是 JavaScript 中一个核心且容易混淆的概念。它的指向不是在定义时确定的,而是在调用时确定的 (即在执行上下文创建时绑定)。本文将系统梳理this的四种绑定规则、优先级以及箭头函数的特殊行为。
一、四种绑定规则
1. 默认绑定(独立函数调用)
当函数以独立函数形式调用(不作为对象的方法,也不通过 call/apply/bind 或 new 调用)时,this 默认绑定到全局对象。
- 浏览器环境:
window - Node.js 环境:
global - 严格模式(
'use strict'):undefined
javascript
function foo() {
console.log(this); // 非严格模式: window; 严格模式: undefined
}
foo();
2. 隐式绑定(方法调用)
当函数作为某个对象的方法被调用时,this 隐式绑定到该调用对象。如果有多层对象嵌套,this 指向最近一层的调用对象。
javascript
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
obj.greet(); // 'Alice'
const outer = {
inner: {
name: 'Bob',
greet() {
console.log(this.name);
}
}
};
outer.inner.greet(); // 'Bob'(this 指向 inner,而非 outer)
3. 显式绑定(主动指定)
通过 call、apply、bind 方法可以主动指定函数的 this 指向,这是最灵活的绑定方式。
call / apply / bind 的区别
| 方法 | 是否立即执行 | 参数传递方式 |
|---|---|---|
| call | 是 | 第一个参数是 this 指向,后续参数逐个列举 |
| apply | 是 | 第一个参数是 this 指向,第二个参数为数组 |
| bind | 否(返回新函数) | 预设 this 和部分参数,返回的新函数永久绑定(硬绑定) |
javascript
function say(age) {
console.log(`${this.name}, ${age}`);
}
const person = { name: 'Tom' };
say.call(person, 25); // Tom, 25
say.apply(person, [25]); // Tom, 25
const bound = say.bind(person, 25);
bound(); // Tom, 25
4. new 绑定(构造函数调用)
当函数通过 new 关键字调用时,作为构造函数,this 绑定到新创建的实例对象。
new 操作符内部执行四个步骤:
- 创建一个空的新对象。
- 将该新对象的
__proto__指向构造函数的prototype。 - 将构造函数的
this绑定到这个新对象。 - 如果构造函数没有返回对象,则返回这个新对象;如果返回了一个对象,则返回该对象(此时
this绑定失败,返回的是手动返回的对象)。
javascript
function Person(name) {
this.name = name;
}
const p = new Person('John');
console.log(p.name); // 'John'
二、优先级:多个规则同时生效时,谁为准?
this 绑定的优先级从高到低为:
new 绑定 > 显式绑定(特别是 bind) > 隐式绑定 > 默认绑定
javascript
function foo() { console.log(this.name); }
const obj1 = { name: 'obj1', foo };
const obj2 = { name: 'obj2', foo };
obj1.foo(); // 'obj1'(隐式绑定)
obj1.foo.call(obj2); // 'obj2'(显式绑定 > 隐式绑定)
const bound = foo.bind(obj1);
bound.call(obj2); // 'obj1'(bind 硬绑定后无法再被 call/apply 改变)
const instance = new bound(); // 如果 foo 不是构造函数则报错,但若可作为构造函数,new 优先级高于 bind(新对象会覆盖 bind 绑定的 this)
注意 :
bind生成的硬绑定函数,其this不会被call/apply覆盖,但new操作可以覆盖bind的绑定(因为new优先级最高)。
三、箭头函数:没有自己的 this 绑定规则
箭头函数不绑定自己的 this ,它的 this 继承自定义时外层词法作用域 (即箭头函数声明时所在上下文的 this),并且无法通过 call、apply、bind 修改。
javascript
const obj = {
name: 'Eve',
greet: () => {
console.log(this.name); // 此处的 this 是外层(全局或上层函数)的 this,不是 obj
}
};
obj.greet(); // undefined(假设全局没有 name)
箭头函数与普通函数的核心区别
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
| this 绑定 | 调用时动态绑定(四种规则) | 定义时继承外层 this,不可变 |
| 构造函数能力 | 可以(new 调用) |
不能(new 会报错) |
| prototype 属性 | 有 | 无 |
| arguments 对象 | 有(类数组) | 无(可用剩余参数 ...args) |
| 简写语法 | function() {} |
() => {},单参数可省略括号 |
特别注意
定义对象时的大括号 {} 不是一个单独的执行环境 ,它依旧处于全局环境中。因此对象字面量内部的方法若使用箭头函数,其 this 仍指向外层作用域(通常是 window 或模块作用域),而不是该对象。
javascript
const obj = {
name: 'Test',
arrow: () => console.log(this.name),
normal() { console.log(this.name); }
};
obj.arrow(); // undefined(this 指向全局)
obj.normal(); // 'Test'
四、总结速查表
| 调用方式 | this 指向 |
|---|---|
| 普通函数独立调用 | 非严格模式:全局对象;严格模式:undefined |
| 对象方法调用 | 该对象(最近一层) |
call/apply/bind |
显式指定的对象(bind 返回的函数不可再改变) |
new 构造函数调用 |
新创建的实例对象 |
| 箭头函数 | 定义时外层作用域的 this(不可改变) |
理解 this 的关键在于记住:不看定义在哪,只看如何调用。