一、基础规则:谁调用,this 就指向谁
js
编辑
var name = "windowName";
var a = {
name: "Cherry",
func1: function() {
console.log(this.name); // 谁调用 func1,this 就是谁
}
};
a.func1(); // 输出 "Cherry" —— a 调用了 func1
✅ 关键点:普通函数作为对象方法被调用时,
this指向该对象。
二、setTimeout 中的 this 陷阱
问题重现
js
编辑
var a = {
name: "Cherry",
func2: function() {
setTimeout(function() {
console.log(this); // ❌ 输出 window(非严格模式下)
this.func1(); // ❌ 报错:this.func1 is not a function
}, 1000);
}
};
a.func2();
原因分析
setTimeout的回调函数是一个普通函数,它是在全局作用域中被调用的。- 因此,
this指向全局对象(浏览器中是window,Node.js 中是global)。 - 即使这个回调写在对象方法内部,也无法自动继承外层的
this。
三、三种主流解决方案对比
方案 1️⃣:使用 that = this(闭包保存上下文)
js
编辑
func2: function() {
var that = this; // 保存当前 this(即 a 对象)
setTimeout(function() {
that.func1(); // ✅ 正确调用
}, 1000);
}
- 优点:兼容性好,ES3 时代就可用。
- 缺点:需要额外变量,代码略显冗余。
方案 2️⃣:使用 bind 显式绑定 this
js
编辑
func2: function() {
setTimeout(function() {
this.func1();
}.bind(this), 1000); // ✅ 强制 this 指向 a
}
或者更常见的方式:
js
编辑
const boundFunc = this.func1.bind(this);
setTimeout(boundFunc, 1000);
-
优点:语义清晰,可复用绑定后的函数。
-
注意:
bind返回一个新函数,不会立即执行。call和apply会立即执行,不适合用于setTimeout的延迟场景。
🔍 小知识:
js 编辑 console.log(a.func1.call(a)); // 立即执行,输出 "Cherry",返回 undefined console.log(a.func1.bind(a)); // 返回绑定后的函数,不执行 const func = a.func1.bind(a); func(); // 手动调用,输出 "Cherry"
方案 3️⃣:使用箭头函数(推荐现代写法)
js
编辑
func2: function() {
setTimeout(() => {
console.log(this); // ✅ 指向 a 对象
this.func1(); // ✅ 正常调用
}, 1000);
}
为什么箭头函数能解决?
- 箭头函数没有自己的
this! - 它的
this词法绑定(lexical binding)到定义时的外层作用域。 - 在
func2中定义的箭头函数,其this继承自func2的this,即a对象。
💡 这是 ES6 最优雅的解决方案,尤其适合事件回调、定时器等场景。
四、深入:call 与 bind 的核心区别
要真正掌握 this 绑定机制,必须厘清 call 和 bind 的本质差异。先记住一句话:
call是「立即执行」函数并绑定this,bind是「返回新函数」(不执行),且永久绑定this/ 参数。
二者的设计目标、执行时机、返回值完全不同。
一、核心差异对比表
| 特性 | call | bind |
|---|---|---|
| 执行时机 | 立即调用原函数 | 不执行原函数,返回「新函数」 |
| 返回值 | 原函数的执行结果 | 绑定了 this/参数的新函数 |
this 绑定 | 仅本次调用有效 | 永久绑定,新函数无法被修改 |
| 参数传递 | 立即传参(call(this, p1, p2)) | 可「预传参」(柯里化),调用新函数时补传剩余参数 |
二、结合 setTimeout 场景,用代码讲透
1. 用 call:立即执行,但失去 setTimeout 的延迟效果
js
编辑
var a = {
name: "cherry",
func1: function() { console.log(this.name) },
func2: function() {
setTimeout(function() {
this.func1();
}.call(this), 1000); // ❌ 错误用法!
}
};
a.func2();
// 结果:立即打印 cherry,完全没有 1 秒延迟!
原因:
call 的本质是「立即调用函数」。你给 setTimeout 传入的不是「函数引用」,而是 call() 执行后的「函数返回值」(这里 func1 没有返回值,所以传入的是 undefined)。
相当于:
js
编辑
// 引擎实际执行逻辑(伪代码)
var callback = (function(){ this.func1(); }).call(a); // callback = undefined
setTimeout(undefined, 1000); // 1 秒后啥也不做
⚠️ 结论:
call不能用于setTimeout的回调绑定!
2. 用 bind:返回新函数,保留延迟效果且绑定 this
js
编辑
var a = {
name: "cherry",
func1: function() { console.log(this.name) },
func2: function() {
setTimeout(function() {
this.func1();
}.bind(this), 1000); // ✅ 正确用法
}
};
a.func2();
// 结果:1 秒后打印 cherry,符合预期!
原因:
bind 不会立即执行函数,而是返回一个「新的函数对象」—— 这个新函数的 this 被永久绑定为你传入的 a,且会被 setTimeout 保存为回调引用。1 秒后引擎调用这个新函数时,this 已经固定为 a,而非 window。
三、更深层:为什么 bind 能「永久绑定」,call 只能「临时」?
call/apply:属于「一次性绑定」,仅在调用的那一刻强制修改函数的this,函数本身的this绑定没有被改变;bind:属于「永久性绑定」,会创建一个「新的函数实例」,新函数的内部属性[[ThisBindingStatus]]被标记为「已绑定」,后续无论用call/apply怎么改,都无法覆盖这个this(除非用new调用)。
验证示例:
js
编辑
function fn() { console.log(this.name); }
var a = { name: "cherry" };
var b = { name: "tom" };
// call 临时修改:本次调用 this 是 a,下次调用恢复
fn.call(a); // 打印 cherry
fn(); // 打印 window.name(或 undefined,严格模式)
// bind 永久修改:新函数的 this 固定为 a
var newFn = fn.bind(a);
newFn(); // 打印 cherry
newFn.call(b); // 依然打印 cherry(bind 绑定无法被覆盖!)
✅ 关键结论:
bind创建的新函数,其this是“锁死”的。
四、什么时候用 call?什么时候用 bind?
| 场景 | 选 call/apply | 选 bind |
|---|---|---|
立即执行函数,临时改 this | ✅ | ❌(bind 不执行) |
延迟执行 / 回调函数(setTimeout、事件监听) | ❌(call 会立即执行) | ✅ |
| 函数柯里化(预传参数) | ❌ | ✅(bind(this, p1) 可预传 p1) |
五、箭头函数特性总结(补充)
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
有 this 吗? | ✅ 有,动态绑定 | ❌ 没有,继承外层 |
可以用 new 吗? | ✅ 可以 | ❌ 不行(无 [[Construct]]) |
有 arguments 吗? | ✅ 有 | ❌ 没有(可用 rest 参数替代) |
| 适合做对象方法吗? | ✅ 适合 | ❌ 不适合(this 不指向对象) |
⚠️ 重要提醒:不要把箭头函数用作对象的方法!
js
编辑
const obj = {
name: "Bad Example",
getName: () => {
return this.name; // ❌ this 不是 obj!
}
};
console.log(obj.getName()); // undefined(this 指向全局)
六、总结要点(速记卡片)
✅ this 指向规则速查:
| 调用方式 | this 指向 |
|---|---|
obj.method() | obj |
func()(独立调用) | 全局对象(非严格模式) / undefined(严格模式) |
setTimeout(fn, ...) | 全局对象 |
| 箭头函数 | 外层作用域的 this |
fn.call(obj) / fn.apply(obj) | obj(立即执行) |
fn.bind(obj) | 返回新函数,this 永久绑定为 obj |
✅ 定时器中正确使用 this 的三种方式:
const that = this;→ 闭包引用.bind(this)→ 显式绑定(✅ 推荐)- 箭头函数 → 自动继承(✅ 现代首选)
✅ call vs bind 一句话总结:
call= 绑定this+ 立即执行 → 适合马上调用;
bind= 绑定this+ 返回新函数(不执行)→ 适合延迟/回调场景。
七、拓展思考
Q:如果在类(class)中使用箭头函数定义方法,会怎样?
js
编辑
class MyClass {
name = "Class Cherry";
regularMethod() {
setTimeout(function() { console.log(this.name); }, 100); // ❌ undefined
}
arrowMethod = () => {
setTimeout(() => { console.log(this.name); }, 100); // ✅ "Class Cherry"
}
}
- 类中的箭头函数方法会自动绑定实例的
this,非常适合用作事件处理器。 - 这也是 React 中常用
handleClick = () => {}的原因。
八、注意事项 & 最佳实践
- 🚫 避免在对象字面量中使用箭头函数作为方法。
- ✅ 在回调、定时器、Promise 链中优先考虑箭头函数。
- 🔁 若需兼容老浏览器(如 IE11),慎用箭头函数,可回退到
bind或that。 - 🧪 调试技巧:在不确定
this时,先console.log(this)看实际指向。 - ⚠️ 永远不要在
setTimeout中对回调使用.call(this)—— 它会破坏延迟逻辑!