在 JavaScript 中,this 是一个让无数开发者困惑的关键字。它不是一个静态的值,而是在函数执行时动态绑定的,其指向取决于函数是如何被调用的。
本文将系统性地讲解 this 的四种调用模式、绑定规则、优先级以及实际应用,帮你彻底掌握这一核心机制。
一、this 是什么?
this是执行上下文中的一个属性,它指向“谁调用了当前函数”。
- 它不是函数定义时决定的,而是运行时(调用时)决定的;
- 它指向最后一次调用该函数的对象(更准确地说,是调用上下文);
- 理解
this是掌握面向对象编程、事件处理、闭包等高级特性的基础。
二、this 的四种调用模式
✅ 1)函数调用模式(Function Invocation)
场景:函数独立调用,不作为对象的属性。
function standalone() {
console.log(this);
}
standalone(); // 浏览器中输出: window
// 严格模式下输出: undefined
📌 规则:
- 非严格模式:
this指向全局对象(浏览器中是window); - 严格模式(
'use strict'):this为undefined。
❗ 这是
this最容易出错的场景,尤其是在回调函数中。
✅ 2)方法调用模式(Method Invocation)
场景:函数作为对象的属性被调用。
const obj = {
name: 'Alice',
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
obj.greet(); // 输出: Hello, I'm Alice ✅
📌 规则:
this指向调用该方法的对象(即.左边的对象);- 这是最符合直觉的
this使用方式。
⚠️ 注意:如果方法被赋值给变量,this 会丢失:
const fn = obj.greet;
fn(); // 输出: Hello, I'm undefined(非严格模式下 `this` 指向 `window`)
✅ 3)构造器调用模式(Constructor Invocation)
场景:使用 new 关键字调用函数。
function Person(name) {
this.name = name;
this.greet = function() {
console.log(`Hi, I'm ${this.name}`);
};
}
const p = new Person('Bob');
p.greet(); // 输出: Hi, I'm Bob ✅
📌 规则:
new会创建一个新对象;this指向这个新创建的实例对象;- 如果构造函数没有显式返回对象,则默认返回
this。
✅ 4)显式绑定模式(Explicit Binding)—— call、apply、bind
场景:手动指定 this 的指向。
(1)call(thisArg, arg1, arg2, ...)
function introduce(age, city) {
console.log(`I'm ${this.name}, ${age} years old, from ${city}`);
}
const person = { name: 'Charlie' };
introduce.call(person, 25, 'Beijing');
// 输出: I'm Charlie, 25 years old, from Beijing
(2)apply(thisArg, [argsArray])
introduce.apply(person, [25, 'Beijing']);
// 效果同上,但参数以数组形式传入
(3)bind(thisArg, arg1, arg2, ...)
const boundFn = introduce.bind(person, 25, 'Beijing');
boundFn(); // 输出同上
// bind 返回一个新函数,`this` 和参数已预设
📌 关键区别:
| 方法 | 是否立即执行 | 参数形式 |
|---|---|---|
call | ✅ 是 | 逐个传参 |
apply | ✅ 是 | 数组传参 |
bind | ❌ 否,返回新函数 | 逐个传参 |
三、this 绑定的优先级
你提到的优先级顺序完全正确!我们来系统梳理:
new > bind > call/apply > 方法调用 > 函数调用
🔝 1. new 的优先级最高
function foo() {
console.log(this.a);
}
foo.prototype.a = 3;
const boundFoo = foo.bind({ a: 1 });
const obj = new boundFoo(); // 忽略 bind 的 this,指向新对象
// obj.a 是 undefined,但原型上有 a=3
console.log(obj.a); // 3
📌 结论:new 会忽略 bind、call、apply 设置的 this。
🔝 2. bind > call/apply
const obj1 = { a: 1 };
const obj2 = { a: 2 };
function foo() {
console.log(this.a);
}
foo.call(obj1); // 1
foo.bind(obj1)(); // 1
foo.bind(obj1).call(obj2); // 仍然输出 1,bind 的 this 不可被覆盖
📌 注意:bind 返回的函数,其 this 是永久绑定的,无法再用 call/apply 修改。
🔝 3. 方法调用 > 函数调用
const obj = {
a: 1,
foo: function() {
console.log(this.a);
}
};
const fn = obj.foo;
fn(); // undefined(或报错,取决于上下文)
obj.foo(); // 1
四、箭头函数中的 this
⚠️ 重要补充:箭头函数没有自己的 this!
- 它的
this继承自外层作用域(词法作用域); - 不能用
call/apply/bind修改; - 不能作为构造函数使用。
const obj = {
a: 1,
regular: function() {
console.log(this.a); // 1
},
arrow: () => {
console.log(this.a); // undefined(继承自全局)
}
};
obj.regular(); // 1
obj.arrow(); // undefined
五、实战技巧:如何判断 this 指向?
使用“调用点分析法”:
new调用? →this指向新对象;call/apply/bind调用? →this指向第一个参数;- 作为对象方法调用? →
this指向该对象; - 否则? → 函数调用模式,非严格模式指向
window,严格模式为undefined。
六、常见陷阱与解决方案
❌ 陷阱1:事件处理中 this 丢失
const button = document.querySelector('button');
button.addEventListener('click', obj.greet); // this 指向 button
✅ 解决方案:
// 方案1:使用 bind
button.addEventListener('click', obj.greet.bind(obj));
// 方案2:使用箭头函数
button.addEventListener('click', () => obj.greet());
❌ 陷阱2:setTimeout 中的 this
obj.delayGreet = function() {
setTimeout(function() {
console.log(this.name); // undefined
}, 1000);
};
✅ 解决方案:
// 方案1:缓存 this
const self = this;
setTimeout(function() { console.log(self.name); }, 1000);
// 方案2:使用 bind
setTimeout(function() { console.log(this.name); }.bind(this), 1000);
// 方案3:使用箭头函数(推荐)
setTimeout(() => console.log(this.name), 1000);
七、总结:this 核心规则一览
| 调用模式 | this 指向 | 优先级 |
|---|---|---|
new Foo() | 新创建的实例 | ⭐⭐⭐⭐⭐(最高) |
foo.bind(obj) | obj(永久绑定) | ⭐⭐⭐⭐ |
foo.call(obj) / foo.apply(obj) | obj | ⭐⭐⭐ |
obj.foo() | obj | ⭐⭐ |
foo() | 全局对象 / undefined | ⭐(最低) |
() => {} | 外层作用域的 this | 特殊 |
💡 结语
“
this不是魔法,而是规则。掌握调用模式,你就掌握了this。”
记住:永远不要猜测 this,而是分析函数是如何被调用的。
在实际开发中:
- 优先使用箭头函数避免
this问题; - 善用
bind固定上下文; - 理解
new的行为,避免误用。