为什么 this 是 JavaScript 面试的永恒考点
this 是 JavaScript 中最令人困惑的特性之一。不同于大多数语言中 this 指向定义时的对象,JavaScript 的 this 在调用时动态决定(箭头函数除外)。理解 this 的绑定规则,是理解对象方法、构造函数、事件处理、React 类组件等所有场景的基础。
一、四条绑定规则
"To understand
this, you have to understand the call-site: the location in code where a function is called (not where it's declared)."— Kyle Simpson, You Don't Know JS: this & Object Prototypes
1.1 默认绑定(Default Binding)
当函数以独立调用(plain, undecorated function call)方式执行时,this 指向全局对象(非严格模式)或 undefined(严格模式)。
function showThis() {
console.log(this);
}
showThis(); // 非严格模式:window(浏览器)/ global(Node)
// 严格模式:undefined
面试要点:严格模式下默认绑定为 undefined,这是很多"意外 TypeError"的来源。
1.2 隐式绑定(Implicit Binding)
当函数作为对象的方法调用时,this 指向调用该方法的对象。
const user = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
user.greet(); // 'Alice' — this → user
隐式丢失(Implicit Loss)——最常见的陷阱:
const fn = user.greet; // 将方法赋值给变量
fn(); // undefined — 隐式绑定丢失,退回默认绑定
// 回调中也会丢失
setTimeout(user.greet, 100); // undefined
[1].forEach(user.greet); // undefined
原理:赋值或传参只是传递了函数引用,调用时不再通过 user. 触发,因此隐式绑定不生效。
1.3 显式绑定(Explicit Binding)
使用 call、apply、bind 明确指定 this。
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const user = { name: 'Alice' };
greet.call(user, 'Hello'); // "Hello, Alice"
greet.apply(user, ['Hello']); // "Hello, Alice"
const bound = greet.bind(user);
bound('Hello'); // "Hello, Alice"
| 方法 | 调用时机 | 参数传递方式 |
|---|---|---|
call |
立即调用 | 逐个传参 |
apply |
立即调用 | 数组传参 |
bind |
返回新函数,延迟调用 | 逐个传参(支持偏函数) |
硬绑定(Hard Binding):bind 创建的函数无法被再次隐式或显式覆盖。
const hardBound = greet.bind(user);
const otherUser = { name: 'Bob' };
hardBound.call(otherUser, 'Hi'); // "Hi, Alice" — 仍然是 Alice
1.4 new 绑定
使用 new 调用构造函数时,this 指向新创建的实例对象。
function Person(name) {
this.name = name;
// 隐式 return this
}
const alice = new Person('Alice');
alice.name; // 'Alice'
new 绑定的完整流程见 prototype-chain 专题中 new 运算符的四步过程。
二、优先级
当多条规则同时适用时,优先级从高到低为:
new 绑定 > 显式绑定(call/apply/bind) > 隐式绑定 > 默认绑定
function foo(val) {
this.a = val;
}
const obj = {};
const bar = foo.bind(obj);
bar(2);
console.log(obj.a); // 2 — 显式绑定生效
const baz = new bar(3);
console.log(obj.a); // 2 — 没有变
console.log(baz.a); // 3 — new 绑定优先级高于 bind
面试要点:new 可以覆盖 bind 的 this 绑定。这是因为 bind 的内部实现检测到 new 调用时会让步。
三、箭头函数的词法 this
箭头函数没有自己的 this,它从定义时的外层词法作用域继承 this。
const user = {
name: 'Alice',
greetLater() {
setTimeout(() => {
console.log(this.name); // 'Alice' — 继承 greetLater 的 this
}, 100);
}
};
箭头函数的 this 不可改变
const arrow = () => console.log(this);
arrow.call({ name: 'Bob' }); // 仍然是外层 this,无法覆盖
arrow.bind({ name: 'Bob' })(); // 仍然是外层 this
new arrow(); // TypeError: arrow is not a constructor
箭头函数不适用的场景
// ❌ 对象方法 — this 不会指向 obj
const obj = {
name: 'Alice',
greet: () => console.log(this.name) // this → 外层(可能是 window)
};
// ❌ 原型方法
Person.prototype.getName = () => this.name; // ❌
// ❌ DOM 事件处理器(期望 this 指向元素)
button.addEventListener('click', () => {
console.log(this); // ❌ 不是 button
});
// ❌ 需要 arguments 对象的场景
const fn = () => console.log(arguments); // ReferenceError
四、特殊场景
4.1 DOM 事件处理器
button.addEventListener('click', function () {
console.log(this); // button 元素 — 隐式绑定由浏览器设置
});
button.addEventListener('click', () => {
console.log(this); // 外层 this(通常是 window)
});
4.2 class 中的 this
class Timer {
count = 0;
// 方法定义 — this 取决于调用方式
tick() {
this.count++;
}
// 箭头函数属性 — this 始终指向实例
tickArrow = () => {
this.count++;
};
}
const timer = new Timer();
const { tick, tickArrow } = timer;
tick(); // ❌ TypeError(严格模式下 this 为 undefined)
tickArrow(); // ✅ this 仍指向 timer 实例
React 类组件中的经典问题:
class App extends React.Component {
handleClick() {
console.log(this); // undefined(如果不 bind)
}
render() {
// 三种修复方案:
return (
<>
<button onClick={this.handleClick.bind(this)}>Bind</button>
<button onClick={() => this.handleClick()}>Arrow wrapper</button>
{/* 或在 constructor 中 this.handleClick = this.handleClick.bind(this) */}
{/* 或使用 class field 箭头函数:handleClick = () => { ... } */}
</>
);
}
}
4.3 globalThis
ES2020 引入 globalThis,统一了不同环境中获取全局 this 的方式:
// 浏览器:globalThis === window
// Node.js:globalThis === global
// Web Worker:globalThis === self
五、手写 call、apply、bind
手写 call
Function.prototype.myCall = function (context, ...args) {
context = context == null ? globalThis : Object(context);
const key = Symbol('temp');
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
手写 apply
Function.prototype.myApply = function (context, args = []) {
context = context == null ? globalThis : Object(context);
const key = Symbol('temp');
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
};
手写 bind
Function.prototype.myBind = function (context, ...boundArgs) {
const fn = this;
return function bound(...args) {
// 支持 new 调用
if (new.target) {
return new fn(...boundArgs, ...args);
}
return fn.apply(context, [...boundArgs, ...args]);
};
};
六、面试高频题型
题型 1:说出输出结果
const obj = {
name: 'obj',
getName() { return this.name; },
getNameArrow: () => this.name
};
console.log(obj.getName()); // 'obj'
console.log(obj.getNameArrow()); // undefined(或 window.name)
const fn = obj.getName;
console.log(fn()); // undefined(严格模式)
题型 2:修复 this 丢失
"给你一个回调函数,如何确保
this指向正确的对象?"
答案要点:bind、箭头函数、class field 箭头函数、闭包变量 const self = this。
题型 3:this 优先级排序
默认 < 隐式 < 显式 < new。能给出代码证明 new 覆盖 bind 的候选人会加分。
七、与其他主题的关联
| 关联主题 | 关系 |
|---|---|
| scope-closure | this 不走词法作用域链(箭头函数除外) |
| prototype-chain | new 绑定是原型继承的核心步骤 |
| async-programming | async 方法中 this 丢失是常见 bug |
| design-patterns-js | 观察者、策略等模式常需处理 this 上下文 |
参考资料
- Kyle Simpson, You Don't Know JS: this & Object Prototypes (2nd Edition) — this 绑定规则的权威讲解
- MDN Web Docs — this
- MDN Web Docs — Function.prototype.bind()
- ECMA-262 — ResolveThisBinding
- Dan Abramov, Overreacted — How Are Function Components Different from Classes? — 涉及 this 在 React 中的问题