“this 到底指向谁?”
几乎每个前端都在控制台里抓狂过。本文带你逐条拆解 this 的四条核心规则、箭头函数特例与常见坑点,附赠记忆口诀与实战 Demo,一次性扫清疑惑。
一、this 是什么?
- 不是函数本身,不是函数词法作用域
- 是“调用者”在运行时偷偷塞进来的隐藏参数——谁把我喊出来,我就指向谁
- 因此 this 与函数声明位置无关,与调用方式有关
二、四条黄金规则(涵盖 99% 场景)
| 规则 | 触发场景 | this 指向 | 严格模式差异 |
|---|---|---|---|
| 1. 默认绑定 | 普通函数直接调用 foo() | 浏览器:window;Node:global | 严格 → undefined |
| 2. 隐式绑定 | 对象方法调用 obj.foo() | 方法所属对象 | 丢失场景见下文 |
| 3. 显式绑定 | call/apply/bind | 手动指定的第一个参数,传 null/undefined 会被忽略 → 回退到全局 | |
| 4. new 绑定 | new Foo() 新创建的实例 | 构造器默认 return this |
记忆口诀:“无点默认,有点隐式,call 显式,new 实例”
三、规则详解 + 代码示例
① 默认绑定
function foo() {
console.log(this.a);
}
var a = 1;
foo(); // 1 (非严格)
'use strict';
foo(); // undefined → 不再指向 window
② 隐式绑定 & 常见的“丢失”陷阱
const obj = {
a: 2,
foo: foo
};
obj.foo(); // 2 → this === obj
隐式丢失三件套(面试高频):
// 1. 赋值
const bar = obj.foo;
bar(); // 1 → 回到默认绑定
// 2. 传参
setTimeout(obj.foo, 0); // 1 → 回调里丢失 obj
// 3. 运算符
(false || obj.foo)(); // 1
破解办法:bind / 箭头函数 / 包装函数
③ 显式绑定
const obj = { a: 3 };
foo.call(obj); // 3
foo.apply(obj, []); // 3
const bar = foo.bind(obj);
bar(); // 永远 3
bind 柯里化:
function add(a, b) { return a + b + this.c; }
const bound = add.bind({ c: 10 }, 5); // 固定 this 与第一个参数
console.log(bound(2)); // 5+2+10 = 17
④ new 绑定
function Animal(name) {
this.name = name;
}
const dog = new Animal('旺财');
console.log(dog.name); // 旺财
构造器显式返回对象会覆盖默认 this:
function Foo() {
this.x = 1;
return { y: 2 }; // 返回新对象 → this 被丢弃
}
console.log(new Foo()); // { y: 2 }
四、箭头函数:this 的“静态快照”
箭头函数没有自己的 this,它捕获外层函数执行上下文的 this(词法作用域)。
function timer() {
this.seconds = 0;
setInterval(() => {
this.seconds++; // 继承 timer 的 this
}, 1000);
}
new timer();
对比普通函数:
setInterval(function () {
this.seconds++; // 丢失 → window.seconds 或报错
}, 1000);
结论
- 事件回调 / 定时器想保留外部 this → 箭头函数最简洁
- 需要动态 this(如 jQuery 插件)→ 不能用箭头函数
五、常见场景速查表
| 代码片段 | this 指向 |
|---|---|
obj.method() | obj |
(obj.method)() | obj(括号不改变) |
(obj.method = obj.method)() | 默认绑定 |
事件侦听器 el.addEventListener('click', handler) | el(DOM 元素) |
内联事件 <button onclick="handler()"> | 全局(非严格) |
箭头函数 const f = () => this | 外层 this |
类方法 class A { m() {} } | 实例(需 new A().m()) |
疑惑:为啥
(obj.method)()与(obj.method = obj.method)()的this指向差异呢?
- 括号是分组运算符,优先级最高,但不做任何求值副作用;所以
(obj.method)()结果仍是原函数引用 obj.method; - 赋值表达式,它是先求值再把求值结果(也就是那个函数本身)返回;所以
(obj.method = obj.method)()赋值完成后,返回的函数引用与 obj 再无任何关联;
六、调试技巧
- 控制台打印:
console.log(this);
debugger; // 断点查看调用栈
- 显式绑定调试:
function logThis() {
console.log(this);
}
logThis.call({ id: 42 }); // 立即确认指向
- 开发环境开启严格模式:
'use strict'; // 让默认绑定变成 undefined,更快暴露错误
七、一句话总结(背下来)
“this 永远指向调用它的对象;没有调用者就回退到全局(或 undefined);箭头函数除外,它只看出生环境。”
八、互动练习
const obj = {
fn: function () {
return this;
},
arrow: () => this
};
console.log(obj.fn()); // ?
console.log(obj.arrow()); // ?
const { fn, arrow } = obj;
console.log(fn()); // ?
console.log(arrow()); // ?
答案:
obj → window → undefined → window
把这篇文章收藏起来,下次再遇到 this 疑难杂症,先问“调用点在哪”,再看“四条规则”,90% 的问题都能秒解。祝你早日驯服这只“变色龙”!