JavaScript 中 this 指向如何确定?
简化版
this 的指向由调用方式决定:普通函数调用默认指向全局对象或严格模式下为 undefined;对象方法调用指向调用者;构造函数调用指向新对象;call/apply/bind 可以显式指定;箭头函数没有自己的 this,继承外层词法作用域。
详细版
常见规则:
fn(); // 默认绑定
obj.fn(); // 隐式绑定,this 是 obj
new Fn(); // new 绑定,this 是新对象
fn.call(obj); // 显式绑定,this 是 obj
箭头函数特殊:
const obj = {
name: 'Tom',
say: () => console.log(this.name),
};
这里 say 的 this 不指向 obj,因为箭头函数没有自己的 this。面试时可以给出优先级:new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定,箭头函数按词法 this 单独看。
完整版教学
一、this 不是定义时决定的
普通函数的 this 大多在调用时决定。下面代码里同一个函数,不同调用方式得到不同 this:
function say() {
console.log(this.name);
}
const a = { name: 'A', say };
const b = { name: 'B', say };
a.say(); // A
b.say(); // B
二、隐式绑定会丢失
把对象方法单独赋值出来后,调用者变了:
const fn = a.say;
fn();
这时不再是 a.say(),所以 this 不再自动指向 a。事件回调、定时器回调里经常遇到这个问题。
三、call、apply、bind 的区别
call 和 apply 都会立即调用函数,区别是参数形式不同:
fn.call(obj, 1, 2);
fn.apply(obj, [1, 2]);
bind 不会立即调用,而是返回一个绑定了 this 的新函数:
const bound = fn.bind(obj);
bound();
四、箭头函数的 this
箭头函数不创建自己的 this,它捕获外层作用域的 this。因此它适合用在回调里保留外层 this,但不适合作为对象原型方法或需要动态 this 的方法。
function Timer() {
this.count = 0;
setInterval(() => {
this.count++;
}, 1000);
}
这里箭头函数让回调里的 this 仍然指向 Timer 实例。
五、面试追问与工程落地
this 题常会追问“箭头函数能不能用 call 改 this”。答案是不能。call/apply/bind 可以传参并调用箭头函数,但无法改变它的词法 this。箭头函数的 this 在定义时从外层捕获,之后不会被调用方式改变。
还要注意 DOM 事件回调。普通函数作为事件监听器时,浏览器通常会把 this 设为绑定事件的元素;箭头函数不会这样。因此如果你需要在监听器里通过 this 拿当前元素,不要写箭头函数,或者直接使用 event.currentTarget。
工程里更推荐显式数据流,少依赖隐式 this。类组件、老 Vue Options API 里 this 很重要;Composition API、React 函数组件和普通工具函数里,很多逻辑可以通过闭包和参数传递,减少 this 丢失问题。
六、按调用表达式和优先级现场判断
判断普通函数的 this,先看是否通过 new 构造,再看显式绑定,随后看调用表达式左侧的 base object,最后才是默认绑定。假设 fn 接收两个参数,fn.call(obj, 1, 2) 与 fn.apply(obj, [1, 2]) 都立即以 obj 为 this;fn.bind(obj, 1) 返回的新函数再传 2 时,参数顺序仍是 1、2。
| 调用形式 | 普通函数的 this | 关键边界 |
|---|---|---|
fn() | 严格模式 undefined;非严格脚本可能替换为全局对象 | ES module 默认严格 |
obj.fn() | 通常是 obj | 提取成变量会丢失 base reference |
fn.call(obj) | 显式指定 obj | 箭头函数不采用该 this |
new Fn() | 新实例 | 构造返回对象时结果另算 |
new (fn.bind(obj))() | 新实例,不是 obj | 预绑定参数仍生效 |
const obj = { value: 1 };
function read() { return this?.value; }
const bound = read.bind(obj);
console.log(bound()); // 1
console.log(bound.call({value: 2})); // 1
bound function 的 this 不能再被普通 call 改写,但它若作为构造器通过 new 调用,构造绑定优先于预绑定 this。箭头函数则没有 [[Construct]],不能使用 new,也没有自己的 arguments、super 或 new.target 绑定。
不要只背“谁调用就指向谁”:必须保留调用表达式、严格模式和函数种类;
const f = obj.m; f()已经丢失 obj 这个调用基对象。
七、常见误区与追问
- 误区:普通函数的
this在定义函数时就固定。 除箭头函数等词法 this 情况外,它由具体调用方式创建的 this binding 决定。 - 误区:对象字面量里的箭头函数会自动把对象作为 this。 对象字面量不会形成箭头函数的 this 作用域,它只捕获更外层词法 this。
- 误区:
bind生成的函数还能被call换成另一个 this。 普通调用时 bound this 已锁定,后续 call 只能传参数,不能覆盖它。 - 追问:
new调用 bound function 时谁优先? 若目标可构造,new创建的新实例成为构造 this,bound this 被忽略,但预绑定参数仍会参与调用。 - 追问:ES module 顶层 this 是什么? 模块顶层 this 为 undefined,且模块代码按严格模式语义执行,不能套用传统非严格 script 的 window 结论。
- 追问:DOM 监听器为什么更推荐
currentTarget? 它显式表示当前监听器所在 EventTarget,不依赖普通函数 this 规则,也避免箭头函数切换后行为变化。 - 追问:class 方法作为回调为什么会丢 this? 方法被提取后只剩函数值,调用表达式不再携带实例基对象,需要 bind、包装箭头函数或显式传实例。
八、加强记忆
this 题抓住一句:普通函数看“谁调用”,箭头函数看“在哪里定义”。再补上四种绑定和优先级,基本就能答完整。