为啥一赋值 this 就变了?搞懂这几件事,轻松拿下

54 阅读5分钟

这段时间背了无数遍:“谁调用,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

面试问法:

callapply 和 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 === obj
  • foo():普通函数调用 → 非严格模式 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 问题:

  1. 先总括:

    this 不是在定义时决定,而是由调用方式在执行期绑定。”

  2. 再列四大场景:

    • 作为对象方法调用 → 指向该对象
    • 作为普通函数调用 → 非严格模式全局对象,严格模式 undefined
    • 作为构造函数调用 → 指向新创建的实例对象
    • call/apply/bind → 显式指定的对象
  3. 最后现场推导题目里的代码,一步步分析调用点是谁。

这个结构非常讨好面试官:既有全局视角,又有代码推理能力

结语

this 真不需要死记硬背,记住两件事就够了:

  • 它是一个“指针”,真正的值永远由调用方式决定
  • 面试时,一定要“先归类到四种场景,再动手推导

你可以自己出几道题,比如:

  • 把对象方法拆出来赋值给变量再调用
  • 在构造函数里再嵌套一个普通函数
  • 混用 call / bind 和 new

按上面的思路演练一遍,面试里遇到 this,就不再是“赌记忆”,而是“照流程推导”。