欢迎使用我的小程序👇👇👇👇
这可能是你学this最轻松的一次
如果你曾被JavaScript中的this搞得晕头转向,别担心——几乎所有JavaScript开发者都经历过这个过程。this是JavaScript中最强大也最令人困惑的概念之一。今天,我将用最简单的方式帮你理清思路。
常见误解:this到底是什么?
误解1:this指向函数本身 ❌
误解2:this指向函数的作用域 ❌
真相:this是在函数被调用时才被绑定的,它的指向完全取决于函数的调用位置,而不是声明位置。
可以把this想象成电话通话中的"我":
- 当我说"我是小明","我"指的是小明
- 当你说"我是小红","我"指的是小红
- "我"这个代词的具体指代,取决于谁在说话
四大绑定规则
让我们通过一个简单的比喻来理解:想象this是一个快递包裹,而不同的绑定规则是包裹上的地址标签。
规则一:默认绑定(收件人:全局或undefined)
当函数被独立调用时,this会默认绑定到全局对象(浏览器中是window,Node.js中是global)。严格模式下是undefined。
function showPackage() {
console.log(this.address); // this指向全局对象
}
address = "全局地址"; // 注意:这是全局变量
showPackage(); // 输出:"全局地址"
严格模式下的不同:
function strictShowPackage() {
"use strict";
console.log(this); // undefined
}
strictShowPackage(); // 输出:undefined
规则二:隐式绑定(收件人:调用者)
当函数作为对象的方法被调用时,this绑定到那个对象。
const person = {
name: "小明",
sayHi: function() {
console.log(`你好,我是${this.name}`);
}
};
person.sayHi(); // 输出:"你好,我是小明"
// 这里this指向person对象
隐式丢失的坑:
const person = {
name: "小明",
sayHi: function() {
console.log(`你好,我是${this.name}`);
}
};
const sayHiFunc = person.sayHi; // 只是复制函数,不是调用
sayHiFunc(); // 输出:"你好,我是undefined"
// 此时是独立调用,this指向全局对象
规则三:显式绑定(指定收件人)
使用call、apply或bind方法,明确告诉函数this应该指向什么。
function introduce(lang, level) {
console.log(`我会${lang},水平${level},我叫${this.name}`);
}
const person1 = { name: "小红" };
const person2 = { name: "小刚" };
// 使用call - 立即执行,参数逐个传递
introduce.call(person1, "JavaScript", "熟练");
// 输出:"我会JavaScript,水平熟练,我叫小红"
// 使用apply - 立即执行,参数数组传递
introduce.apply(person2, ["Python", "精通"]);
// 输出:"我会Python,水平精通,我叫小刚"
// 使用bind - 返回新函数,稍后执行
const introPerson1 = introduce.bind(person1, "Java", "一般");
introPerson1(); // 输出:"我会Java,水平一般,我叫小红"
硬绑定(不会丢失的绑定):
// 使用bind创建硬绑定函数
const person = {
name: "小明",
sayHi: function() {
console.log(`你好,我是${this.name}`);
}
};
// 硬绑定:无论怎么调用,this都固定指向person
const boundSayHi = person.sayHi.bind(person);
boundSayHi(); // 输出:"你好,我是小明"
// 即使作为回调函数,this也不会丢失
setTimeout(boundSayHi, 100); // 输出:"你好,我是小明"
规则四:new绑定(新建收件人)
使用new关键字调用构造函数时,this会绑定到新创建的对象。
function Person(name) {
// new调用时,this指向新创建的空对象
this.name = name;
this.sayHi = function() {
console.log(`你好,我是${this.name}`);
};
// 不需要return,构造函数会自动返回this
}
const xiaoming = new Person("小明");
xiaoming.sayHi(); // 输出:"你好,我是小明"
new操作符做了四件事:
- 创建一个全新的空对象
- 将这个新对象的
[[Prototype]]链接到构造函数的prototype - 将
this绑定到这个新对象 - 如果函数没有返回其他对象,则自动返回这个新对象
特殊场景与优先级
箭头函数:没有自己的this
箭头函数不遵循上述规则,它的this由外层作用域决定。
const obj = {
name: "小明",
regularFunc: function() {
console.log(this.name); // 指向obj
},
arrowFunc: () => {
console.log(this.name); // 指向外层作用域的this
}
};
obj.regularFunc(); // 输出:"小明"
obj.arrowFunc(); // 输出:undefined(如果外层是全局)
优先级总结
当多个规则同时适用时,优先级如下:
- new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
- 箭头函数的
this由外层决定,不受这些规则影响
function showThis() {
console.log(this.name);
}
const obj1 = { name: "obj1", showThis };
const obj2 = { name: "obj2" };
// 隐式绑定
obj1.showThis(); // 输出:"obj1"
// 显式绑定优先级更高
obj1.showThis.call(obj2); // 输出:"obj2"
// new绑定优先级最高
const newObj = new obj1.showThis(); // 输出:undefined(新对象没有name属性)
实用技巧与常见坑
1. 回调函数中的this丢失
// 常见问题
const button = {
name: "提交按钮",
onClick: function() {
console.log(this.name); // 期望:"提交按钮"
}
};
// this丢失了!
document.querySelector("button").addEventListener("click", button.onClick);
// 点击后输出:undefined
// 解决方案1:使用bind
document.querySelector("button").addEventListener("click", button.onClick.bind(button));
// 解决方案2:使用箭头函数
const button2 = {
name: "提交按钮",
onClick: function() {
console.log(this.name);
},
init: function() {
document.querySelector("button").addEventListener("click", () => {
this.onClick(); // 箭头函数的this指向button2
});
}
};
2. 链式调用
const calculator = {
value: 0,
add: function(num) {
this.value += num;
return this; // 返回this实现链式调用
},
multiply: function(num) {
this.value *= num;
return this;
},
getValue: function() {
return this.value;
}
};
const result = calculator.add(5).multiply(2).add(10).getValue();
console.log(result); // 输出:20
快速决策流程图
遇到this问题,按这个流程判断:
graph TD
A[判断this指向] --> B{函数调用方式?};
B --> C[new调用];
C --> D[this指向新创建的对象];
B --> E[call/apply/bind调用];
E --> F[this指向指定的第一个参数];
B --> G[对象方法调用 obj.func ];
G --> H[this指向obj];
B --> I[独立调用 func ];
I --> J{是否是箭头函数?};
J --> K[是];
K --> L[this指向外层作用域的this];
J --> M[否];
M --> N{是否严格模式?};
N --> O[是];
O --> P[this为undefined];
N --> Q[否];
Q --> R[this指向全局对象];
总结
理解this的关键是记住:this是在函数被调用时确定的,而不是定义时。四大规则为你提供了判断依据:
- 默认绑定:独立调用 → 全局/undefined
- 隐式绑定:对象方法调用 → 该对象
- 显式绑定:call/apply/bind调用 → 指定的对象
- new绑定:构造函数调用 → 新创建的对象
加上箭头函数的例外规则,你就能应对绝大多数this相关的问题了。
最好的学习方式是实践。打开浏览器的开发者工具,多写几个例子,观察不同情况下this的表现。当你真正理解this的运作机制后,你会发现它不再是障碍,而是编写灵活、强大JavaScript代码的有力工具。