引言
"这个 this 怎么又 undefined 了?"
"为什么在对象方法里 this 指向对象,回调里就指向 window?"
"箭头函数里的 this 怎么跟我预想的不一样?它是不是有自己的想法?"
如果你被 this 折磨过,你不是一个人。我曾经在一个 Vue 组件里写了一个 setTimeout,里面用了 this.message,结果打印出来是 undefined。我对着屏幕怒吼:"这不科学!我明明就在组件实例内部!"
后来我才明白:this 不是看你在哪里定义的,而是看你怎么调用的。它就像一个社交达人,谁叫它,它就跟谁走。今天,我们就来扒一扒 this 的"社交规则"。
一、this 的四大绑定规则(法官宣判版)
1.1 默认绑定:单身狗模式
当你直接调用一个普通函数,且不在严格模式下,this 指向全局对象(浏览器里是 window,Node 里是 global)。严格模式下是 undefined。
javascript
function showThis() {
console.log(this);
}
showThis(); // window(非严格模式)
这叫"默认绑定"------函数被独立调用,没有主人,this 就只好认全局做爹。
1.2 隐式绑定:有对象罩着
当函数作为对象的方法被调用时,this 指向那个对象。
javascript
const obj = {
name: '张三',
greet() {
console.log(this.name);
}
};
obj.greet(); // '张三' ------ this 指向 obj
但如果把这个方法拿出来单独调用,就回到默认绑定:
javascript
const fn = obj.greet;
fn(); // undefined(或 window.name,如果存在)
这就是传说中的"丢失 this"------你把方法从对象身上扒下来,它就失去了归属感。
1.3 显式绑定:硬塞给你
用 call、apply、bind 可以强行指定 this。
javascript
function greet() {
console.log(this.name);
}
const user = { name: '李四' };
greet.call(user); // '李四'
bind 会返回一个新函数,永久绑定 this,再也改不了(除非你用 new 去怼)。
1.4 new 绑定:造物主模式
当函数被 new 调用时,this 指向新创建的那个对象。
javascript
function Person(name) {
this.name = name;
}
const p = new Person('王五');
console.log(p.name); // '王五'
这四条规则优先级:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定。
二、箭头函数:this 的"叛逆少年"
箭头函数不绑定自己的 this,它会捕获定义时所在的 this,并且永远不变。
javascript
const obj = {
name: '赵六',
greet: () => {
console.log(this.name);
}
};
obj.greet(); // undefined(因为箭头函数的 this 是定义时的外层 this,此处外层是 window)
箭头函数最适合用在回调里,比如 setTimeout、事件监听,能保留外层 this。
javascript
const obj = {
name: '孙七',
greet() {
setTimeout(() => {
console.log(this.name); // '孙七' ------ 箭头函数保住了 this
}, 100);
}
};
三、经典坑位大赏
坑1:回调函数里的 this
javascript
const obj = {
name: '周一',
greet() {
setTimeout(function() {
console.log(this.name); // undefined 或 window.name
}, 100);
}
};
解决方案:用箭头函数,或者在外面缓存 const self = this。
坑2:事件监听里的 this
javascript
button.addEventListener('click', function() {
console.log(this); // 指向 button 元素
});
如果用箭头函数,this 就变成外层(通常是 window),可能不是你想要的。
坑3:类方法作为回调
javascript
class MyClass {
constructor() {
this.name = '实例';
}
handle() {
console.log(this.name);
}
}
const inst = new MyClass();
setTimeout(inst.handle, 100); // undefined ------ 方法被单独传递,丢失 this
修复:setTimeout(() => inst.handle(), 100) 或 setTimeout(inst.handle.bind(inst), 100)。
四、一句话记住 this
this 指向调用它的那个对象,箭头函数除外,它指向定义时的爹。
更精确版:谁调用我,我就指向谁;没人调用,我就指向全局(严格模式 undefined);箭头函数不跟别人走,我出生时的环境就是我的家。
五、实战:写出一个安全的 this 代码
javascript
class SearchBox {
constructor(inputEl) {
this.inputEl = inputEl;
this.inputEl.addEventListener('input', this.handleInput.bind(this));
// 或者用箭头函数:this.handleInput = (e) => { ... }
}
handleInput(e) {
console.log(this); // 指向 SearchBox 实例,而不是 input 元素
this.search(e.target.value);
}
search(keyword) {
console.log(`搜索: ${keyword}`);
}
}
六、总结:别再被 this 坑了
this 是 JavaScript 中最容易出错的概念之一,但掌握了绑定规则,它就是个纸老虎。记住四个绑定(默认、隐式、显式、new),加上箭头函数的特例,你就能在面试和工作中游刃有余。
下次当你看到 this 变成 undefined 时,先问自己三个问题:
- 这个函数是怎么被调用的?(默认/隐式/显式/new)
- 是不是箭头函数?
- 是不是在严格模式下?
答完这三个问题,凶手就找到了。
每日一问:你曾经遇到过最诡异的 this 指向 bug 是什么?是在 React 组件里?还是在 Vue 的 methods 里?评论区分享你的"this 历险记"!