1. 引言
在 JavaScript 中,this 的指向问题一直是初学者乃至资深开发者最容易混淆的知识点之一。很多人在面试或实际开发中,面对 this 的指向时常常一头雾水。其实,理解 this 的关键只有一句话:this 的指向取决于函数的调用位置(调用方式),而不是定义位置。
本文将系统性地梳理 JavaScript 中 this 的五大绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定,以及箭头函数的特殊行为,并通过大量可运行的代码示例,帮助你彻底掌握 this 的指向规律。
2. 核心概念:调用位置决定一切
在深入各种绑定规则之前,我们首先要建立一个核心认知:this 是在函数被调用时确定的,它指向谁,取决于这个函数是被谁、以什么方式调用的。
javascript
function showThis() {
console.log(this);
}
// 直接调用:this 指向全局对象(浏览器中为 window)
showThis(); // window
// 作为对象方法调用:this 指向该对象
const obj = {
name: 'obj',
show: showThis
};
obj.show(); // obj
同一个函数,仅仅因为调用方式不同,this 的指向就完全不同。这就是「调用位置决定一切」的含义。
3. 默认绑定
默认绑定是最简单的一种情况:当函数独立调用 (不带任何修饰)时,this 指向全局对象。
javascript
function foo() {
console.log(this);
}
foo(); // 浏览器中输出 window,Node.js 中输出 global
在严格模式下,默认绑定的 this 会是 undefined:
javascript
'use strict';
function foo() {
console.log(this);
}
foo(); // undefined
注意 :在 ES6 模块中,代码默认处于严格模式,因此独立调用函数时
this为undefined。
4. 隐式绑定
当函数作为某个对象的方法 被调用时,this 隐式地指向该对象。这是最常见的绑定方式。
javascript
const person = {
name: 'Alice',
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
person.greet(); // Hello, I am Alice
4.1 隐式丢失
隐式绑定有一个常见的陷阱:隐式丢失 。当方法被赋值给另一个变量再调用时,this 会丢失原来的绑定,退化为默认绑定。
javascript
const person = {
name: 'Alice',
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
const greet = person.greet;
greet(); // Hello, I am undefined(this 指向全局对象)
4.2 回调函数中的隐式丢失
将方法作为回调传入时,同样会发生隐式丢失:
javascript
const person = {
name: 'Alice',
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
setTimeout(person.greet, 1000); // Hello, I am undefined
5. 显式绑定
显式绑定是通过 call、apply、bind 三个方法,强制指定 函数执行时的 this 指向。
5.1 call 与 apply
call 和 apply 都会立即执行函数,区别仅在于传参方式:call 逐个传参,apply 以数组传参。
javascript
function introduce(city, age) {
console.log(`${this.name} lives in ${city}, age ${age}`);
}
const person = { name: 'Bob' };
introduce.call(person, 'Beijing', 25); // Bob lives in Beijing, age 25
introduce.apply(person, ['Shanghai', 30]); // Bob lives in Shanghai, age 30
5.2 bind
bind 不会立即执行函数,而是返回一个绑定了 this 的新函数,之后可以随时调用。
javascript
function introduce(city) {
console.log(`${this.name} lives in ${city}`);
}
const person = { name: 'Carol' };
const boundIntroduce = introduce.bind(person);
boundIntroduce('Guangzhou'); // Carol lives in Guangzhou
5.3 显式绑定解决隐式丢失
利用 bind,我们可以修复前面回调函数中的隐式丢失问题:
javascript
const person = {
name: 'Alice',
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
setTimeout(person.greet.bind(person), 1000); // Hello, I am Alice
6. new 绑定
使用 new 关键字调用函数(此时函数作为构造函数)时,this 会指向新创建的对象实例。
javascript
function Person(name, age) {
this.name = name;
this.age = age;
}
const alice = new Person('Alice', 25);
console.log(alice.name); // Alice
console.log(alice.age); // 25
new 绑定做了四件事:
- 创建一个全新的空对象;
- 将这个空对象的原型指向构造函数的
prototype; - 将
this绑定到这个新对象上; - 如果构造函数返回了对象,则返回该对象;否则返回新创建的对象。
javascript
function Person(name) {
this.name = name;
// 没有显式 return,默认返回 this 指向的新对象
}
const bob = new Person('Bob');
console.log(bob.name); // Bob
7. 箭头函数与优先级
箭头函数是 ES6 引入的特殊函数,它不绑定自己的 this ,而是继承外层作用域的 this。箭头函数的 this 在定义时就确定了,与调用方式无关。
javascript
const person = {
name: 'Alice',
greet: () => {
console.log(`Hello, I am ${this.name}`);
}
};
person.greet(); // Hello, I am undefined(this 继承自外层作用域,即全局)
7.1 箭头函数解决回调中的 this 问题
箭头函数非常适合用于回调场景,因为它不会改变 this 的指向:
javascript
const person = {
name: 'Alice',
greet() {
setTimeout(() => {
console.log(`Hello, I am ${this.name}`);
}, 1000);
}
};
person.greet(); // Hello, I am Alice(箭头函数继承了 greet 的 this)
7.2 箭头函数不能作为构造函数
箭头函数没有自己的 this,因此不能使用 new 调用:
javascript
const Person = (name) => {
this.name = name;
};
// 报错:Person is not a constructor
// const p = new Person('Alice');
7.3 箭头函数无法被显式绑定
由于箭头函数不绑定 this,call、apply、bind 对它无效:
javascript
const arrow = () => {
console.log(this);
};
const obj = { name: 'obj' };
arrow.call(obj); // 仍然输出全局对象,而不是 obj
8. 绑定优先级总结
当多种绑定规则同时出现时,优先级从高到低为:
new绑定(最高)- 显式绑定 (
call/apply/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
// new 绑定优先级最高
const bound = foo.bind(obj1);
new bound(); // undefined(new 绑定覆盖了 bind 的 this)
8.1 箭头函数不参与优先级
箭头函数不遵循上述优先级规则,它的 this 完全由定义时的外层作用域决定,任何绑定方式都无法改变。
9. 实战案例与常见陷阱
9.1 事件处理中的 this
在 DOM 事件处理中,this 默认指向触发事件的元素:
javascript
const button = document.querySelector('#btn');
button.addEventListener('click', function() {
console.log(this); // button 元素
});
// 使用箭头函数时,this 指向外层作用域
button.addEventListener('click', () => {
console.log(this); // window(或外层作用域)
});
9.2 对象方法中的嵌套函数
javascript
const obj = {
name: 'obj',
outer() {
function inner() {
console.log(this); // 独立调用,this 为全局对象
}
inner();
}
};
obj.outer(); // window
解决方案:使用箭头函数或保存 this:
javascript
const obj = {
name: 'obj',
outer() {
const inner = () => {
console.log(this); // 继承 outer 的 this,指向 obj
};
inner();
}
};
obj.outer(); // obj
9.3 解构赋值中的 this 丢失
javascript
const person = {
name: 'Alice',
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
const { greet } = person;
greet(); // Hello, I am undefined(隐式丢失)
10. 总结与速查表
| 绑定方式 | 触发条件 | this 指向 |
|---|---|---|
| 默认绑定 | 独立函数调用 | 全局对象(严格模式下为 undefined) |
| 隐式绑定 | 作为对象方法调用 | 调用该方法的对象 |
| 显式绑定 | call / apply / bind | 手动指定的对象 |
| new 绑定 | new 调用构造函数 | 新创建的实例对象 |
| 箭头函数 | 定义时确定 | 外层作用域的 this |
核心记忆口诀:
- 独立调用看全局;
- 方法调用看对象;
- call / apply / bind 手动指定;
- new 创建新实例;
- 箭头函数看外层,绑定规则全失效。
掌握 this 的指向规律,是深入理解 JavaScript 作用域、闭包、原型链等高级特性的基础。希望本文能帮助你彻底告别 this 指向的困惑。