🧭 一次把 this 讲透:JS 开发者的进阶指北
如果说 JavaScript 是一座城市,
那this就是它的交通规则。
新手靠直觉开车,老手靠规则通行。
而高手,早已把规则内化于心。
你已经写过不少 JS 代码,知道 this 大致指向调用者;
你也用过箭头函数、bind、甚至在类和事件处理器里处理过上下文问题。
但每当遇到嵌套回调、动态绑定、或框架封装下的 this,
是不是仍会心头一紧,暗自嘀咕:
“这次 this 到底绑的是谁?”
这篇文章不讲基础语法,也不重复“谁调用就指向谁”的入门口诀。
我们聚焦于 this 绑定机制的深层逻辑与实战陷阱,
帮你从“能用”走向“精通”,彻底告别 this 的不确定性。
准备好了吗?让我们深入 this 的运行时世界。
一、先抛结论:this 到底是什么?
一句话先记住:
this不是函数定义时决定的,而是在函数被调用时动态绑定的
可以把 this 理解成一句话里的我。
我在吃饭我在跑步
“我是谁?”
要看这句话是谁说的。
二、最基础的一条规则:谁调用,this 指向谁
先看一个非常典型的代码场景:
var name = 'windowName';
var a = {
name: "Cherry",
func1: function(){
console.log(this.name);
}
}
a.func1();
输出结果是:
Cherry
为什么?
因为:
func1是通过a.func1()调用的- 所以
this === a this.name === "Cherry"
👉 对象.方法() → this 指向这个对象
就像你喊:
“我是 Cherry”
那“我”自然是 Cherry 本人。
三、this 真正让人崩溃的地方:定时器
在 JavaScript 中,this 丢失是常见问题(如异步回调、函数赋值后调用等场景),其核心原因是:函数的实际调用方式发生了改变,导致
this指向意外切换。下面以定时器为例,介绍几种常用防止 this 丢失的方法
接下来,进入重灾区。
var name = 'windowName';
var a = {
name: "Cherry",
func1: function(){
console.log(this.name);
},
func2:function(){
setTimeout(function(){
console.log(this);
this.func1();
},1000)
}
}
a.func2();
很多人以为 1 秒后会输出:
Cherry
但实际情况是
this指向windowthis.func1根本不存在- 程序直接报错 💥
四、为什么 this 在定时器里“变了”?
这不是 JS 在针对你,而是调用方式变了。
我们拆开来看:
setTimeout(function(){
...
},1000)
这个函数是谁调用的?
👉 不是 a,而是由宿主环境(如浏览器)在全局上下文中调用的。
setTimeout的回调函数是一个普通函数,它在将来被宿主环境(如浏览器)以“独立函数调用”的形式执行(即callback()),而不是作为某个对象的方法调用。根据 JavaScript 的this绑定规则,这种调用方式会将this绑定到全局对象(非严格模式下)或undefined(严格模式下)。
等价于:
window.setTimeout(fn, 1000);
于是:
- 定时器里的
this === window - 和外面的
a已经没有关系了
五、解决方案一:that = this(最朴素)
这是最老牌、最原始、但最好理解的一种方式。
var a = {
name: "Cherry",
func1: function(){
console.log(this.name);
},
func2:function(){
let that = this;
setTimeout(function(){
that.func1();
},1000)
}
}
a.func2(); // 1秒后,输出cherry
原理非常简单:
this会随调用方式改变;- 但普通变量(如
that)不会变; - 所以提前捕获并复用。
📌 this 会变,但变量不会
六、解决方案二:bind —— 给 this 订婚 💍
var a = {
name: "Cherry",
func1: function(){
console.log(this.name);
},
func2:function(){
setTimeout(function(){
this.func1();
}.bind(a),1000)
}
}
a.func2();// 1秒后,输出cherry
bind 做了什么?
bind返回一个新函数,其this永远被固定为你传入的对象
- 不会立刻执行
- this 永远绑定为你指定的对象
call / apply / bind 的关键区别
a.func1.call(a); // 立即执行
a.func1.apply(a); // 立即执行
const fn = a.func1.bind(a); // 不执行
fn(); // 现在才执行
| 方法 | 是否立即执行 | this 是否固定 |
|---|---|---|
| call | ✅ | ❌ |
| apply | ✅ | ❌ |
| bind | ❌ | ✅ |
📌 bind 更像婚约,call/apply 更像临时邀请
七、解决方案三:箭头函数(现代 JS 首选)
var a = {
name: "Cherry",
func1: function(){
console.log(this.name);
},
func2:function(){
setTimeout(() => {
console.log(this);
this.func1();
},1000)
}
}
a.func2();// 1秒后,输出cherry
为什么箭头函数可以?
八、箭头函数的本质:它根本没有 this
箭头函数不绑定自己的 this,而是词法地继承外层作用域的 this。
const func = () => {
console.log(this);
}
func();
new func();
输出结果:
这意味着:
- 箭头函数中的
this= 定义时所在上下文的this; - 它不会被
call、apply、bind或new改变; - 它不能作为构造函数使用。
九、三种解决方案怎么选?
| 方式 | 特点 | 适合场景 |
|---|---|---|
| that = this | 直观、好理解 | 学习 / 老代码 |
| bind | this 固定、安全 | 回调 / 事件 |
| 箭头函数 | 最简洁 | 现代开发首选 |
📌 能用箭头函数,就优先用箭头函数
十、最后总结(一定要记住)
this 的三条铁律
1️⃣ this 永远和“调用方式”有关
2️⃣ 普通函数的 this 会变
3️⃣ 箭头函数没有 this,只继承外层
🌟 写在最后
this 并不可怕,
真正可怕的是:
“凭感觉写 this”
一旦你开始用:
- “谁调用?”
- “是不是普通函数?”
- “是不是箭头函数?”
这三句话去分析,
你会发现:
this 其实非常诚实——它只是忠实地反映了调用现场