JavaScript的this坑人实录,这次我真的记下来了
|
zhenglin
2026年11月2日 10:30
本文热度 44
|
上周凌晨两点,我在紧急修复一个线上问题时,发现了一个诡异的 bug:明明调用了
user.checkPermission(),权限却始终返回
false。而当我用
console.log 打印
this 时,它居然指向了
window!——这个经典的
this 丢失问题,又一次用血淋淋的现实教育了我。
如果你也写过类似这样的代码,我打赌你也踩过这个坑:
const user = {
name: '张三',
checkPermission: function() {
return this.name === '管理员'; // 期待this指向user
}
};
// 某个事件回调中这样调用
button.onclick = user.checkPermission; // 点击时this神秘消失了!
为什么this会“丢”?
问题的根源在于 this 的绑定规则。与大多数语言不同,JavaScript 的 this 不是由声明时的上下文决定的,而是由调用时的“点号左边”决定的(专业术语叫 执行上下文绑定)。
直接调用(如 obj.method())时,this 指向 obj
赋值后调用(如 const fn = obj.method; fn())时,this 指向全局(严格模式下是 undefined)
回调函数(如 setTimeout(obj.method, 100))时,this 同样会丢失
这就是为什么我们的 checkPermission 方法在作为事件回调时,this 突然变成了 window(或 undefined)。
不只是回调:这些场景也会丢this
陷阱1:异步函数中的this
class DataFetcher {
constructor() {
this.data = null;
}
fetch() {
axios.get('/api/data').then(function(response) {
this.data = response.data; // 报错!这里的this是undefined
});
}
}
axios.get('/api/data').then((response) => {
this.data = response.data; // 箭头函数不绑定自己的this
});
或提前绑定:
const _this = this;
axios.get('/api/data').then(function(response) {
_this.data = response.data;
});
陷阱2:方法作为参数传递
const utils = {
log: function() { console.log(this.name); },
name: 'Utils'
};
// 假设有一个第三方库接受函数参数
thirdPartyLib.doWork(utils.log); // 输出undefined,this丢了!
thirdPartyLib.doWork(utils.log.bind(utils));
或者包裹一层箭头函数:
thirdPartyLib.doWork(() => utils.log());
陷阱3:class方法未绑定
class Button {
constructor() {
this.text = 'Click me';
}
handleClick() {
console.log(this.text); // 这里会报错!
}
}
const btn = new Button();
document.addEventListener('click', btn.handleClick); // this丢失!
constructor() {
this.handleClick = this.handleClick.bind(this);
}
handleClick = () => {
console.log(this.text); // 箭头函数自动绑定实例this
};
性能对比:bind vs 箭头函数
有人可能会问:.bind 和箭头函数有什么区别?在实际项目中,我做过一个简单的性能测试:
| 方式 | 调用100万次耗时 | 内存占用 |
|---|
| 直接调用 | 15ms | 最低 |
| .bind | 220ms | 较高 |
| 箭头函数 | 18ms | 中等 |
结论:.bind 有性能开销,但在大多数场景下可以忽略。箭头函数更接近原生性能,但会占用更多内存(每个实例都会创建新函数)。
避坑清单:this的7条军规
永远记住:this 取决于调用方式,而非声明位置
在回调中使用箭头函数(setTimeout、Promise、事件监听)
Class 方法要么用 .bind,要么用 class fields 箭头函数
避免将对象方法直接作为参数传递(除非显式绑定)
在严格模式下测试(this 表现更安全,不会悄悄指向 window)
慎用 apply/call:它们会覆盖默认绑定规则
在 TypeScript 中开启 --noImplicitThis 编译选项
最后
经过这次深夜 debug,我终于把 this 的坑刻进了 DNA。现在每当我写回调函数时,手指都会不自觉地先敲出箭头函数——这是被坑过的人才懂的肌肉记忆。
该文章在 2026/11/2 10:30:03 编辑过