这段时间背了无数遍:“谁调用,this 指向谁”,但一到面试写代码,还是会懵:
var foo = obj.fn; foo()为啥不是指向obj?- 构造函数里的
this到底是“函数”还是“实例”? - 普通函数里
this为什么是全局对象?严格模式又为什么变成undefined?
这篇文章不讲口号,只给你一套表达思路,能在未来的学习尤其是面试过程中游刃有余
一、先记住一句话:this 跟“定义位置”无关,只跟“调用方式”有关
面试官很爱问:
“
this是在什么阶段绑定的?”
高分答案要点:
- 普通变量、作用域这些,大多在编译阶段就确定好了
this是个例外,它在“执行阶段”由“调用方式”决定- 所以:看调用点,不看定义处
这一句话,是你后面所有推导的“总纲”。
二、四大场景:把 90% 的面试题一网打尽
1. 作为对象方法调用:this → 调用它的对象
var obj = {
name: '极客时间',
show: function () {
console.log(this.name);
}
};
obj.show(); // this === obj
面试问法:
“在对象方法中,
this指向什么?”
回答要点:
this指向调用这个函数的对象- 上例中,调用点是
obj.show(),调用者是obj,所以this === obj
常见坑:
var foo = obj.show;
foo(); // this 是谁?
- 现在调用点变成了
foo(),不再有“点”前面的对象,进入“普通函数调用场景”(下一节)
2. 作为普通函数调用:非严格模式 this → 全局对象
function fn() {
console.log(this);
}
fn(); // 非严格模式下:this === window(浏览器)
为什么是全局对象?
-
普通函数里其实根本不需要
this -
语言早期的设计比较“粗糙”:既然
this必须有个值,那就干脆指向全局对象 -
所以:
var声明的变量会挂在window上,容易产生全局污染let/const不会挂在window上,是后来的改进- 严格模式干脆“不给你乱指了”,直接让普通函数里的
this = undefined
高分回答模版:
- 普通函数直接调用(非严格模式)时,
this默认绑定到全局对象- 严格模式下则是
undefined,从语言层面规避了“莫名其妙污染全局”的情况
3. 构造函数调用:this → 新创建的实例
function Person(name) {
console.log(this);
this.name = name;
}
var p = new Person('极客时间');
关键是这个 new,它背后做了 4 件事(面试高频):
function myNew(Ctor, ...args) {
// 1. 创建空对象
var obj = {};
// 2. 设置原型
obj.__proto__ = Ctor.prototype;
// 3. 以 obj 为 this 调用构造函数
Ctor.call(obj, ...args);
// 4. 返回这个对象
return obj;
}
var p = myNew(Person, '极客时间');
你可以这样回答面试官:
- 使用
new调用时,JS 引擎会自动创建一个空对象,并把this绑定到这个对象上- 构造函数内部通过
this.xxx = ...,其实就是在给这个新对象添加属性- 最后这个对象被返回,作为实例
常见误解:
“
console.log(this)打印出来是Person { ... },所以 this 指向构造函数 Person?”
正确理解:
- 真实的
this是“一个被Person构造出来的对象” - 控制台显示
Person { ... }只是用构造函数名作为“标签” - 验证方法:
console.log(this instanceof Person); // true
console.log(this.__proto__ === Person.prototype); // true
4. call / apply / bind 显式绑定:this → 你传入的那个对象
function show() {
console.log(this.name);
}
var obj = { name: '极客时间' };
show.call(obj); // this === obj
show.apply(obj); // this === obj
var bound = show.bind(obj);
bound(); // this 也是 obj
面试问法:
“
call、apply和bind有什么区别?”
高分要点:
call(thisArg, ...args):立即调用,参数按序列表达apply(thisArg, argsArray):立即调用,参数以数组形式bind(thisArg, ...args):不立即调用,返回一个永久绑定 this 的新函数
顺手加一句:
这三个 API 是“手动指定调用点”,从而手动指定
this的绑定。
三、面试官爱下手的坑:你得会“现场推导”
例子 1:方法被“拆出来”调用
var obj = {
name: '极客时间',
show() {
console.log(this.name);
}
};
var foo = obj.show;
foo(); // ?
obj.show(); // ?
推导过程:
obj.show():方法调用 →this === objfoo():普通函数调用 → 非严格模式this === window,严格模式this === undefined
关键句:不是看函数“之前属于谁”,而是看“现在是谁在调用”
例子 2:构造函数里调普通函数
function inner() {
console.log(this);
}
function Outer() {
console.log(this);
inner();
}
new Outer();
面试官期待你这样说:
-
new Outer():- 第一行
console.log(this):this是新创建的实例对象
- 第一行
-
inner():- 是普通函数调用
- 非严格模式:
this === window - 严格模式:
this === undefined
再顺手强调一句:
“可以看出
this是在调用时动态绑定的,哪怕是在构造函数内部调用别的函数,也要单独看那个函数的调用方式。”
四、如何在面试里“答得很专业”
你可以用这个结构来回答任何 this 问题:
-
先总括:
“
this不是在定义时决定,而是由调用方式在执行期绑定。” -
再列四大场景:
- 作为对象方法调用 → 指向该对象
- 作为普通函数调用 → 非严格模式全局对象,严格模式
undefined - 作为构造函数调用 → 指向新创建的实例对象
call/apply/bind→ 显式指定的对象
-
最后现场推导题目里的代码,一步步分析调用点是谁。
这个结构非常讨好面试官:既有全局视角,又有代码推理能力。
结语
this 真不需要死记硬背,记住两件事就够了:
- 它是一个“指针”,真正的值永远由调用方式决定
- 面试时,一定要“先归类到四种场景,再动手推导”
你可以自己出几道题,比如:
- 把对象方法拆出来赋值给变量再调用
- 在构造函数里再嵌套一个普通函数
- 混用
call/bind和new
按上面的思路演练一遍,面试里遇到 this,就不再是“赌记忆”,而是“照流程推导”。