【前端面试小册】JS-第13节:面试必会 - this 专题

一、this 的概念

1.1 定义

this 是 JavaScript 语言的关键字之一,this 既不指向函数自身也不指向函数的词法作用域。

核心理解this 是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

类比理解this 就像一个"指向标",它的指向不是固定的,而是根据函数的调用方式动态决定的。就像同一个"指向标"在不同的人手里,指向的方向也不同。

1.2 关键点

  • this 的绑定发生在函数调用时,而不是函数定义时
  • this 的指向完全取决于函数的调用方式
  • this 不是静态的,是动态的

二、this 绑定规则

2.1 默认绑定

函数直接调用,不带修饰(callapply 等)会有如下规则:

  • 非严格模式:浏览器环境 this 指向 window,Node 环境 this 指向 Global
  • 严格模式this 指向 undefined

Demo 1:基本默认绑定

var name = '成都巴菲特'

var obj = {
    name: '知识星球:前端职场圈',
    log: function() {
        console.log(this.name)
    }
}

var log = obj.log

log()  // 浏览器中输出: "成都巴菲特"

分析

  • log 直接调用,没有任何修饰,所以走默认绑定规则
  • this 指向 window,而 var 定义的全局变量会默认挂在 window
  • this.name = window.name 输出:成都巴菲特

注意:如果这里直接在 Node 环境下输出的是 undefined,因为 this 指向 Global,而 var 定义的变量不会挂在 Global 上。

Demo 2:setTimeout 中的默认绑定

var name = '成都巴菲特';
var person = {
    name: '公众号:前端面试资源',
    log: log
}

function log() {
    setTimeout(function() {
        console.log('Hi,', this.name);
    })
}

person.log();
setTimeout(function() {
    person.log();
}, 200);

// 输出结果
// Hi, 成都巴菲特
// Hi, 成都巴菲特

分析

  • 函数的参数是函数,比如 setTimeout,参数(函数)中的 this 在非严格模式指向全局对象(浏览器:window,Node 环境:Global
  • setTimeout 的回调函数中的 this 指向全局对象

2.2 隐式绑定

函数调用的时候是否在上下文中调用,或者说是否某个对象调用函数。

Demo 1:普通情况

var name = '成都巴菲特';
var person = {
    name: '公众号:前端面试资源',
    log: function() {
        console.log('Hi,', this.name);
    }
}

person.log();  // 公众号:前端面试资源

分析log 作为对象属性调用的,隐式绑定会把函数中 this 指向绑定到这个上下文对象 person

Demo 2:嵌套调用情况

在多层嵌套调用函数时,如:对象.对象.函数this 指向的是最后一层对象

function log() {
    console.log('Hi', this.name);
}

var child = {
    name: '成都巴菲特',
    log: log
}

var father = {
    name: 'koala',
    child: child
}

father.child.log();  // Hi 成都巴菲特

分析this 指向的是最后一层对象 child,而不是 father

2.3 显式绑定

通过函数 callapplybind 可以修改函数 this 的指向。

知识点回顾

call、apply 与 bind 区别

  • callapply:改变 this 指向同时执行函数
  • bind:改变 this 指向,但是返回函数,而不是执行函数

call 与 apply 区别

func.call(this, arg1, arg2)        // call 用法
func.apply(this, [arg1, arg2])     // apply 用法

// 第一个参数都是 this 所绑定的对象
// call 接受的参数列表,apply 接受参数数组

Demo 1:基本显式绑定

var person = {
    name: "成都巴菲特",
};

function log(name, job) {
    this.name = name;
    this.job = job;
}

log.call(person, '程序员', '前端开发');
console.log(person.name);  // '程序员'

log.apply(person, ['程序员', '前端开发']);
console.log(person.job);  // '前端开发'

Demo 2:显式绑定成功

function sayHi() {
    console.log('hi,', this.name);
}

var person = {
    name: '成都巴菲特',
    sayHi: sayHi
}

var name = '程序员';
var hi = person.sayHi;
hi.call(person);  // hi, 成都巴菲特

Demo 3:显式绑定失效?

function sayHi() {
    console.log('hi,', this.name);
}

var person = {
    name: '成都巴菲特',
    sayHi: sayHi
}

var name = '程序员';
var hi = function(fn) {
    fn();
}

hi.call(person, person.sayHi);  // hi, 程序员

分析

  • demo2 明明用了 call 来改变 this 指向是显式绑定,为什么 this.name 不是 person 里面的 name 值'成都巴菲特',而是全局变量中的'程序员'?

原因

  • 因为执行:hi.call(person, person.sayHi) 的时候是把 hithis 绑定在 person
  • 但是 fn() 的调用没有改变 this 指向,所以走默认规则
var hi = function(fn) {
    fn();  // 直接调用,没有改变 this 指向
}

// 等价于
var hi = function(fn = sayHi) {
    sayHi();  // 直接调用,走默认绑定
}

Demo 4:解决显式绑定失效

function sayHi() {
    console.log('hi,', this.name);
}

var person = {
    name: '成都巴菲特',
    sayHi: sayHi
}

var name = '程序员';
var hi = function(fn) {
    fn.call(this);  // 将 fn 的 this 绑定到 hi 的 this(即 person)
}

hi.call(person, person.sayHi);  // hi, 成都巴菲特

分析:因为我们改变的是 hithis 指向,并没有改变 fnthis 指向,那么把 fnthis 指向绑定到 hi 上,其实就是相当于绑定在了 hi 指向的 person

2.4 new 绑定

使用 new 会执行如下流程:

  1. 创建一个空对象
  2. 将空对象的 __proto__ 指向原对象的 prototype
  3. 执行构造函数中的代码
  4. 返回这个新对象

Demo

function Person(name) {
    this.name = name;
}

var p = new Person('成都巴菲特');
console.log(p.name);  // 成都巴菲特

知识点回顾:实现一个 new

function myNew() {
    // 创建的新对象
    let obj = {};
    
    // 第一个参数是构造函数
    let [constructor, ...args] = [...arguments];
    
    // 执行[[原型]]连接;obj 是 constructor 的实例
    obj.__proto__ = constructor.prototype;
    
    // 执行构造函数,将属性或方法添加到创建的空对象上
    let result = constructor.apply(obj, args);
    
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
        // 如果构造函数执行的结果返回的是一个对象,那么返回这个对象
        return result;
    }
    
    // 如果构造函数返回的不是对象,那么返回我们创建对象
    return obj;
}

2.5 绑定优先级

有的时候函数可能会应用多种规则,这个时候按照他们优先级来判断:

优先级规则new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定

// 示例
function log() {
    console.log(this.name);
}

var obj1 = { name: 'obj1', log: log };
var obj2 = { name: 'obj2', log: log };

// 显式绑定 > 隐式绑定
log.call(obj1);  // obj1(显式绑定)
obj1.log();      // obj1(隐式绑定)

// new 绑定 > 显式绑定
var boundLog = log.bind(obj1);
var instance = new boundLog();  // undefined(new 绑定覆盖了显式绑定)

2.6 绑定例外

applycallbindthis 新指向对象如果是 nullundefined 会失效,实际走默认绑定。

var obj = {
    name: '成都巴菲特'
}
var name = '程序员';

function log() {
    console.log(this.name);
}

log.call(null);  // 程序员(null 失效,走默认绑定)

三、箭头函数的 this

3.1 箭头函数特点

  • 没有自己的 this,继承外层代码块 this
  • 不能作为构造函数,所以不能使用 new
  • 没有 arguments 对象
  • 不能使用 yield,所以不能作为 Generator 函数
  • 没有自己的 this,无法使用 callapplybind 去显式改变 this 指向

3.2 箭头函数 this 示例

var name = '成都巴菲特'
let name1 = 'name1'

let obj = {
    name: '程序员',
    log: () => {
        console.log(this.name)
    },
    log1: () => {
        console.log(this.name1)
    },
    log2: function() {
        return () => console.log(this.name)
    }
}

obj.log()   // 成都巴菲特(箭头函数继承外层 this,即 window)
obj.log1()  // undefined(let 声明的变量不在 window 上)
const log2 = obj.log2()
log2()      // 程序员(箭头函数继承 log2 的 this,即 obj)

3.3 箭头函数 this 不是静态的

注意:箭头函数是继承外层 this,但不是静态的。

var obj = {
    sayHi: function() {
        return function() {
            console.log(this);
            return () => {
                console.log(this);
            }
        }
    }
}

let sayHi = obj.sayHi();
let fun1 = sayHi();  // 输出 window
fun1();              // 输出 window

let fun2 = sayHi.bind(obj)();  // 输出 obj
fun2();                         // 输出 obj

分析

let fun2 = function() {
    // 这里应用默认规则 this(假设是 this1)指向 window
    console.log(this);
    
    return () => {
        // 这里的 this 假设是(this2),因为箭头函数没有自己的 this,
        // 所以 this2 默认继承外面的 this1 也就是 window
        console.log(this);
    }
}

然而:let fun2 = sayHi.bind(obj)() 改变了 this 的指向,让 this1 指向了 obj,而 this2 继承 this1 所以也指向了 obj

四、面试题

4.1 题目 1:let vs var 在箭头函数中的 this

let name = '知识星球:前端职场圈'
let obj = {
    name: '微信公众号:前端面试资源',
    test: () => {
        console.log(this.name)
    }
}

obj.test()  // 输出是什么?

答案undefined

解析

  • 使用 let 声明的变量,调用函数的 this 指向 window
  • let 声明的变量挂在 Script 上,所以 window.nameundefined

4.2 题目 2:var vs let 在箭头函数中的 this

var name = '知识星球:前端职场圈'
let obj = {
    name: '微信公众号:前端面试资源',
    test: () => {
        console.log(this.name)
    }
}

obj.test()  // 输出是什么?

答案知识星球:前端职场圈

解析

  • 使用 var 声明的变量,调用函数的 this 指向 window
  • var 声明的变量挂在 window 上,所以 window.name 有值

知识点回顾letvar 在全局中声明变量区别?

  • let:在全局中创建的变量存在于 Script 中,它与 windowGlobal)平级
  • var:在全局中创建的变量存在于 windowGlobal)中

4.3 题目 3:复杂箭头函数 this

var obj = {
    log1: function() {
        console.log(this);
        return () => {
            console.log(this);
        }
    },
    log2: function() {
        return function() {
            console.log(this);
            return () => {
                console.log(this);
            }
        }
    },
    log3: () => {
        console.log(this);
    }
}

let log1 = obj.log1();
log1();               
let log2 = obj.log2();
let fun1 = log2(); 
fun1();             
obj.log3();

答案

let log1 = obj.log1();  // 输出 obj 对象
log1();                 // 输出 obj 对象
let log2 = obj.log2();
let fun1 = log2();      // 输出 window
fun1();                 // 输出 window
obj.log3();             // 输出 window

分析

  1. obj.log1():隐式绑定规则,this 绑定在 obj 上,所以输出 obj
  2. log1():这一步执行的就是箭头函数,箭头函数继承上一个代码块的 this,刚刚我们得出上一层的 thisobj,显然这里的 this 就是 obj
  3. log2():这个时候相当于在全局重新定义了一个 log2 的函数,这个时候 this 执行的是默认绑定,this 指向的是全局对象 window
  4. fun1():这里 fun1 是箭头函数,this 是继承于外层代码块的 this,此时外层 this 指向的是 window,因此这儿的输出结果是 window
  5. obj.log3()obj.log3 也是箭头函数,this 继承外层 objthis,但是代码块 obj 中是不存在 this 的,只能往上找,就找到了全局的 this,指向的是 window

五、this 绑定规则总结

5.1 绑定规则流程图

graph TD
    A[函数调用] --> B{是否使用 new?}
    B -->|是| C[new 绑定: this = 新对象]
    B -->|否| D{是否使用 call/apply/bind?}
    D -->|是| E[显式绑定: this = 指定对象]
    D -->|否| F{是否作为对象方法调用?}
    F -->|是| G[隐式绑定: this = 调用对象]
    F -->|否| H{是否箭头函数?}
    H -->|是| I[继承外层 this]
    H -->|否| J[默认绑定: this = window/undefined]

5.2 优先级总结

  1. new 绑定new 调用时,this 指向新创建的对象
  2. 显式绑定callapplybind 调用时,this 指向指定对象
  3. 隐式绑定:作为对象方法调用时,this 指向调用对象
  4. 默认绑定:直接调用时,this 指向全局对象(非严格模式)或 undefined(严格模式)
  5. 箭头函数:继承外层代码块的 this

六、面试要点总结

核心知识点

  1. this 的绑定时机:函数调用时,不是定义时
  2. 绑定规则:new > 显式 > 隐式 > 默认
  3. 箭头函数:没有自己的 this,继承外层
  4. 特殊情况call/apply/bind 传入 null/undefined 会失效

常见面试题

Q1: this 的指向规则有哪些?

答:有四种绑定规则:new 绑定、显式绑定(call/apply/bind)、隐式绑定(对象方法调用)、默认绑定(直接调用)。优先级:new > 显式 > 隐式 > 默认。

Q2: 箭头函数的 this 指向什么?

答:箭头函数没有自己的 this,继承外层代码块的 this。无法通过 callapplybind 改变。

Q3: 如何改变 this 的指向?

答:

  1. 使用 callapplybind(显式绑定)
  2. 使用 new(new 绑定)
  3. 作为对象方法调用(隐式绑定)
  4. 箭头函数无法改变(继承外层)

实战建议

  • ✅ 理解 this 的绑定规则和优先级
  • ✅ 注意箭头函数和普通函数的 this 区别
  • ✅ 理解 callapplybind 的使用场景
  • ✅ 注意 letvar 在全局作用域的区别对 this 的影响
#前端面试##前端#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

2025-11-11 19:32
中国传媒大学 运营
辞职那天,我爸打了我一巴掌2023年3月15日,我走进公司HR办公室,递交了辞职信。做销售三年,月薪过万,在老家算得上体面工作。但我清楚,这不是我想要的生活。当天晚上我回家吃饭,跟父母说了这个决定。我妈当场就哭了:"你疯了吗?好好的工作不做,28了还要去学编程?"我爸更直接,啪一巴掌扇过来:"我看你是脑子坏了!你一个文科生,凭什么觉得自己能学编程?"那一巴掌其实不疼,疼的是他说的那句话:"凭什么?"是啊,我凭什么?没有计算机基础,数学高考只考了89分,大学学的是市场营销。除了会用Office和浏览器,我对计算机一无所知。但我还是决定试试。因为在上一家公司,我看到技术部的同事们,他们不用朝九晚五陪客户吃饭喝酒,不用周末还要回复客户信息,更不用看人脸色点头哈腰。他们有一门手艺,是靠技术吃饭。我想成为那样的人。培训班的180天,我是最拼的那个2023年4月,我咬牙报了一家培训机构,学费2万8。那是我全部的积蓄。第一天上课,我就发现自己跟不上。老师讲的什么变量、函数、循环,听起来像天书。班里30个人,有一半是计算机专业的应届生,他们听得懂老师讲的梗,会讨论技术问题。而我,连"IDE是什么"都要偷偷百度。第一周结束,我差点就要放弃了。转机发生在第8天。那天晚上,我一个人在培训机构的自习室待到凌晨2点,死磕一道题。题目是用Java写一个计算器,我写了删,删了写,始终报错。突然,教室门开了,进来一个穿着格子衬衫的中年男人。他是培训班的创始人,很少露面。他看了我一眼:"还在写代码?""嗯,有道题不会。"我有点尴尬。他走过来看了看我的屏幕,5秒钟就指出了问题:"你这里方法名写错了,Java是区分大小写的。"改完之后,程序成功运行。我第一次体会到那种成就感。他拍了拍我肩膀:"我看你的课堂记录,你是班上最努力的那个。但有一点要记住:编程不是靠努力就能学会,要学会方法。"那天晚上,他给我讲了很多学习方法:别死记语法,学会查官方文档不要只听课,要大量做项目遇到bug先自己调试,实在不行再问别人建立自己的知识体系,用思维导图整理我把这些话记在笔记本第一页,每天看一遍。接下来5个月,我像变了个人:每天早上7点到教室,晚上12点离开周末别人放假,我在自习室刷题老师布置3个作业,我自己再找5个类似题型练习遇到不懂的问题,我会查10篇博客、3个视频,直到完全理解班上的同学都说我疯了。但我知道,我没有退路。28岁转行,我没资格慢慢来。第一次面试,我被问哭了2023年9月,培训结束,我信心满满地开始投简历。第一周,投了80份,0回复。第二周,投了120份,收到2个面试邀请,都是小公司。第一次技术面试,是一家创业公司。面试官是个90后,看起来比我小,但他的问题我答不上来:"说说Java的垃圾回收机制?""不太清楚...""Spring Boot的自动配置原理?""没深入了解...""你这个项目用了Redis,说说缓存击穿怎么解决?""这个...我没遇到过..."面试持续了20分钟,他合上电脑:"回去等通知吧。"我知道,没戏了。走出那栋写字楼,已经是傍晚6点。路上车水马龙,我站在人行道上,突然就哭了。不是因为面试被拒,而是突然觉得自己很可笑:28岁了,还在和22岁的应届生竞争。人家是科班出身,我凭什么?那天晚上,我一个人喝了两瓶啤酒,在小区楼下坐到凌晨3点。第二天醒来,我做了一个决定:既然科班的优势我没有,那我就创造别的优势。疯狂做项目的60天我开始疯狂做项目。培训班的项目太基础了,都是跟着视频敲的代码,面试官一问细节就露馅。我需要真正属于自己的项目。项目一:健身房管理系统我从自己的生活找需求。我办了健身卡,发现健身房的管理很混乱:约私教要打电话,经常没人接不知道器械是否空闲会员卡快到期了没人提醒我花了2周,做了一个健身房管理系统:会员端:在线预约私教、查看器械使用情况、到期提醒教练端:管理学员、记录训练计划、收入统计管理端:会员管理、财务报表、设备维护记录做完之后,我厚着脸皮找到健身房老板,说可以免费给他们用。老板试用了一周,真的开始用了!这个项目给了我信心:我做的东西是有人用的,不是demo。项目二:二手交易小程序我发现小区业主群里经常有人转卖二手物品,但交易很不方便。我用2周时间,做了一个小程序:LBS定位,只显示3公里内的商品即时聊天,买卖双方可以直接沟通信用评分,防止恶意交易上线第一周,我在小区群里发了链接,当天就有50个人注册。一个月后,用户突破200人,真实交易80多笔。这个数据让我在面试时有了底气。项目三:智能面试助手这是我最用心的项目。我发现自己准备面试时很痛苦:不知道会被问什么问题不知道怎么回答才算好面试完不知道哪里没答好我花了3周,做了一个面试助手系统:输入简历和岗位JD,AI生成可能的面试问题提供参考答案和回答思路模拟面试,录音后给出改进建议技术栈用了Spring Boot + Vue + Python(调用通义千问API做问题生成)。这个项目最大的亮点是:我自己就是用户,我知道痛点在哪。做这些项目的过程中,我又用到了泡泡小程序的AiCV简历王。因为我需要把项目经历写到简历上,但我不知道怎么描述才能吸引面试官。我把项目的开发文档和功能截图输入进去,它帮我生成了结构化的项目描述,包括背景、技术栈、核心功能、数据成果这些。虽然我后来又改了很多,但至少给了我一个框架,让我知道该写什么。60天,3个完整项目,GitHub上的commit记录密密麻麻。现在,我的简历不再是空洞的"掌握Java、Spring、MySQL",而是:健身房管理系统:真实商用,服务200+会员二手交易小程序:200+用户,80+真实交易智能面试助手:创新项目,GitHub 30+ stars转机2023年11月,我又开始投简历。这次不一样了。一周内,我收到了6个面试邀请。第一场技术面试,是一家中型互联网公司。面试官问我:"你这个智能面试助手挺有意思,说说怎么实现的?"这次我准备充分了:"这个项目分为三个模块:简历解析、问题生成和答案优化..."我讲了架构设计、技术选型、遇到的问题、解决方案,整整讲了15分钟。面试官听得很认真,中途还问了几个细节问题,我都答上来了。"你这个项目确实做得不错。"他点点头,"虽然你是培训班出身,但能看出来你很用心。"那场面试,我过了。接下来的二面、三面,HR面,我也都顺利通过。11月30日,我收到了offer邮件:后端开发工程师,月薪12k。虽然不高,但对我来说已经是巨大的突破。回头看这段经历,我想分享一些真实的感受:1. 转行没有想象中那么难,但也没有那么容易难在:年龄大了,学习能力确实不如应届生没有计算机基础,很多东西要从头学市场竞争激烈,HR更倾向要年轻人不难在:方法对了,3-6个月真的能学会基础实际工作中,很多问题靠的是经验和业务理解,不是算法你有社会经验,知道怎么和人打交道,这是应届生没有的2. 项目经验比证书重要100倍培训班会给你发结业证书,但那玩意儿一文不值。面试官看的是:你做过什么真实项目?项目解决了什么问题?有没有真实用户和数据?我的3个项目,每一个都是真实运行的,有用户有数据。这比培训班的10个demo项目更有说服力。3. 学会讲故事很重要技术是硬实力,表达是软实力。我在面试中学会了讲故事:不好的回答:"我做了一个健身房管理系统,用了Spring Boot和Vue。"好的回答:"我发现小区健身房的管理很混乱,经常约不到私教。所以我用2周时间,做了一个管理系统。上线后,健身房老板真的在用,现在服务了200多个会员,预约效率提升了50%。技术栈用的是..."看出区别了吗?后者有场景、有问题、有解决方案、有成果。但我现在的想法是:如果不尝试,5年后我还会后悔今天没有开始。年龄是劣势,但也是优势。我有社会经验,知道用户需求,理解业务逻辑,这些是应届生学不来的。给想转行的你如果你也在考虑转行程序员,我的建议是:1. 想清楚为什么要转行不要因为"听说程序员工资高"就转行。问自己:我对写代码有兴趣吗?我能接受长期学习吗?我能接受前期低薪吗?如果答案都是YES,那就试试。2. 选对学习路径自学 vs 培训班:自学:成本低,但容易迷茫,战线拉得太长培训班:有系统的课程,但要选靠谱的机构我选的是培训班,因为我想快速入门。但培训班只是起点,后面的学习靠自己。3. 简历要会写培训班出身最大的问题是:简历不会写。我之前的简历就是流水账,后来学会了:用STAR法则描述项目用数据说话突出亮点和成果临投递前,我还会用AiCV简历王再检查一遍,看看简历和JD的匹配度,查漏补缺。这个工具对我这种不知道怎么写简历的转行人特别有用。
秋招白月光
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务