本文将从最基础的对象方法中this的指向说起,深入剖析定时器中this"不听话" 的原因,再逐一讲解几种常见的 "救回this" 的方法,包括经典的var that = this、灵活的call/apply、实用的bind,以及 ES6 中更优雅的箭头函数。通过清晰的案例对比和原理分析,帮你彻底理清this的绑定规律,从此不再被this的指向问题困扰。
一、从最普通的对象方法说起:this 指向当前对象
先从最正常的场景看起:一个对象,里面有个方法,方法里打印
this 和 this.name。
js
var obj = {
name: 'Cherry',
func1: function () {
console.log(this);
console.log(this.name);
}
};
obj.func1();

在这种通过"对象.方法()"调用的场景下:
- this 指向的是
obj本身 this.name就是"Cherry"
也就是说,只要是"谁点出来的函数,
this 一般就指向谁"。
这一点很多人都懂,真正乱的是下面这种情况。
二、一进定时器,this 就不听话了
把上面的对象稍微改一下:再加一个方法,里面开个定时器。
js
var obj = {
name: 'Cherry',
func1: function () {
console.log(this.name);
},
func2: function () {
console.log(this); // 这里的 this 还是 obj
setTimeout(function () {
console.log(this); // 这里的 this 是谁?
this.func1(); // 这里很多人第一反应是"调用不到"
}, 3000);
}
};
obj.func2();
运行之后你会发现:

- func2 里面第一行
console.log(this)打印的是obj - 但是定时器回调里的
console.log(this),却不再是obj,而是全局对象(浏览器里是window,严格模式下甚至可能是undefined)
原因是:谁调用这个函数,
this 就指向谁。
- obj.func2() 是"对象.方法调用",所以
this === obj setTimeout回调是"普通函数调用",真正执行时类似window.callback(),所以 this 又回到了全局
于是
this.func1() 就出现了典型错误:
你以为是调用 obj.func1,实际上是在全局环境下找 func1。
三、三种常见的"救回 this"姿势
为了在定时器里还能拿到"外层的那个对象",常见有三种写法。
1. 老派写法:var that = this
最早接触到的方案一般是这个:
js
var obj = {
name: 'Cherry',
func1: function () {
console.log(this.name);
},
func2: function () {
var that = this; // 先把外层的 this 存起来
setTimeout(function () {
console.log(this); // 这里还是全局对象
that.func1(); // 用 that 调用
}, 3000);
}
};
obj.func2();

思路很直白:
- 外层 this 是我们想要的对象
- 回调内部再用一个变量
that把它"闭包"住 - 不再依赖回调里的 this,而是用
that去调用
优点:
- 所有环境都支持,ES5 就可以用
缺点: - 可读性一般,多层回调时会出现
that = this/self = this满天飞
2. call / apply:立即执行并指定 this
第二种方案是利用 Function.prototype.call / apply,它们有两个关键点:
- 都是立即调用函数
- 第一个参数是要绑定的 this
例如:
js
function show() {
console.log(this.name);
}
var obj = { name: 'Cherry' };
show(); // this => window / undefined
show.call(obj); // this => obj
show.apply(obj); // this => obj
call 和 apply 的区别只在于传参方式:
call(fnThis, arg1, arg2, ...)apply(fnThis, [arg1, arg2, ...])
在和定时器结合时,有一种稍微"绕"一点的写法,会先用 call 执行一次,然后把返回的函数交给定时器:
js
setTimeout(function () {
console.log(this); // 这里的 this 被 call 成 obj
this.func1();
return function () {
console.log(this); // 这个函数真正被 setTimeout 调用时,this 又回到全局
};
}.call(obj), 2000);
分析一下这个写法的流程:
.call(obj)先立刻执行 这段函数,里面的 this 是obj- 这个函数里 this.func1() 能正常调用到 obj.func1
- 它
return的那个内部函数才是真正交给setTimeout的 - 这个内部函数在将来执行时,又是一次"普通函数调用",于是 this 再次回到全局
这种写法属于"利用 call 硬拉一次
this 过来",但在实际项目里,更常见的做法不是这样用 call,而是第三种:bind。
3. bind:先订婚,后结婚
bind 和 call/apply 很容易混:
call/apply:马上执行,并临时指定一次 thisbind:不执行,而是返回一个"this 永远被绑死"的新函数
用一个简单对比看差异:
js
var obj = {
name: 'Cherry',
func1: function () {
console.log(this.name);
}
};
console.log(obj.func1.bind(obj)); // 打印的是一个新函数
console.log(obj.func1.call(obj)); // 打印的是 func1 的返回值(这里是 undefined)
const f = obj.func1.bind(obj);
f(); // 始终以 obj 作为 this 调用

套用一个比较形象的说法:
call/apply:闪婚,当场拍板,函数当场执行完事bind:先订婚,先约定好将来的 this,真正结婚(执行)是以后
因此在定时器这种"将来才会执行"的场景,bind 非常自然:
js
var obj = {
name: 'Cherry',
func1: function () {
console.log(this.name);
},
func2: function () {
setTimeout(this.func1.bind(this), 3000);
}
};
obj.func2();
this.func1.bind(this)立即返回了一个新函数- 这个新函数里 this 被固定成当前对象
setTimeout三秒后再执行它时,this 依然是那个对象
相较于 that = this 和"花里胡哨的 call 写法",bind 在这种场景下是最容易读懂的一种。
四、箭头函数:不再创建自己的 this
还有一种办法,是直接 **放弃回调自己的 **
this,而是用外层的。
这就是箭头函数的做法:箭头函数不会创建自己的执行上下文,它的 this 完全继承自外层作用域。
箭头函数的核心是没有自己的
this,它的this是词法绑定 (定义时继承外层作用域的this),而非动态绑定。
把前面的定时器改成箭头函数版本:
js
var obj = {
name: 'Cherry',
func1: function () {
console.log(this); // obj
console.log(this.name); // Cherry
},
func2: function () {
console.log(this); // obj
setTimeout(() => {
console.log(this); // 依然是 obj
this.func1(); // 也能正常调用
}, 3000);
}
};
obj.func2();
obj.func1();

这里的关键点是:
- func2 里的 this 是对象本身
- 箭头函数的 this 直接沿用 func2 的 this
- 所以在箭头函数里,this 没有发生"跳变",始终是那个对象
再结合一个简单的对比例子,看得更清楚。
1. 普通函数和箭头函数的 this 对比
js
// 普通函数
function normal() {
console.log(this);
}
// 箭头函数
const arrow = () => {
console.log(this);
};
normal(); // 非严格模式下 this => window
arrow(); // this 继承自定义它时所在的作用域(全局里一般也是 window / undefined)

再注意一个常被问到的问题:
- 箭头函数不能作为构造函数使用 ,也就是说不能
new一个箭头函数
在实际代码里,如果你尝试new func()(func 是箭头函数),会直接报错
2. 顶层箭头函数的 this
在普通脚本里,如果写一个顶层箭头函数:
js
const func = () => {
console.log(this);
};
func();

这里的
this 继承自顶层作用域:
- 浏览器非模块脚本中,一般是
window - 严格模式 / ES 模块中,顶层 this 往往是
undefined
这也再次说明:箭头函数的
this 完全取决于它被定义时所处的环境,而不是被谁调用。
3. 继承外层作用域的 this
js
const obj = {
name: 'Cherry',
func: function () { // 普通函数,this === obj
console.log('外层 this:', this);
setTimeout(() => { // 箭头函数,this 继承外层
console.log('箭头函数 this:', this);
console.log('name:', this.name);
}, 1000);
}
};
obj.func();

把 setTimeout 里的箭头函数换成普通函数,this 会丢失
setTimeout中的回调函数是被 JavaScript 引擎 "独立调用" 的,而非作为某个对象的方法调用。
五、小结 & 使用建议
把上面的内容串一下,可以得到这样一份"速记表":
this 的基础规律
-
谁调用,指向谁 :
obj.method()里this === obj -
普通函数直接调用:
fn()里 this 是全局对象 /undefined(严格模式) -
setTimeout/ 回调里的this
- 回调是普通函数调用,this 默认指向全局
- 所以在对象方法里直接写
setTimeout(function () { this.xxx }),往往拿不到我们想要的对象
-
三种修复方式的对比
-
var that = this- 利用闭包保存外层 this
- 兼容最好,但代码略显"老派"
-
call/apply- 立即执行函数
- 第一个参数用来指定 this
- 适合"当场就要执行一次"的场景
-
bind- 返回"this 被锁死"的新函数,而不是立即执行
- 非常适合"定时器、事件监听、回调"这些"稍后再执行"的情形
-
-
箭头函数
- 不创建自己的 this,只继承外层
- 在对象方法中配合回调使用,能有效避免 this 跳来跳去
- 不适合作为构造函数(不能
new)