当前位置: 首页 > news >正文

【面试题】这道 JS 经典面试题不要背!今天帮你彻底搞懂它

前言

这是一道非常经典的面试题,涵盖了从函数的基本概念、运算符优先级,到作用域链、原型链、this关键字、new关键字等基础知识点考察,可以说能完整答对 JS 基础才算过了关,本文就带大家一起回顾这道面试题,彻底搞懂它。

1、前端面试题库 (面试必备)            推荐:★★★★★

面试题库地址:前端面试题库

// a
function Foo () {
 getName = function () {
   console.log(1);
 }
 return this;
}
// b
Foo.getName = function () {
 console.log(2);
}
// c
Foo.prototype.getName = function () {
 console.log(3);
}
// d
var getName = function () {
 console.log(4);
}
// e
function getName () {
 console.log(5);
}

按顺序执行后分别输出什么?

  1. Foo.getName();
  2. getName();
  3. Foo().getName();
  4. getName();
  5. new Foo.getName();
  6. new Foo().getName();
  7. new new Foo().getName();

先自己尝试写出结果再看答案,后面是详细解析。

答案

Foo.getName();           // 2
getName();               // 4
Foo().getName();         // 1
getName();               // 1 
new Foo.getName();       // 2
new Foo().getName();     // 3
new new Foo().getName(); // 3

解析

1. Foo.getName()

这一问首先考察的是函数的基本概念:在 JS 中函数是第一类对象,也被称作"一等公民",这是因为函数拥有对象所拥有的全部功能。所以这里的 Foo.getName() 可以看作是调用了 Foo 对象上的属性,在题目中的 b 处有其定义,故结果输出 2 。

2. getName()

这里调用的 getName 在上下文中被定义了两次,一次是通过变量声明,一次是函数声明,故这一问考察的是变量声明提升与函数声明提升,声明提前会让声明提升到代码的最上层,而函数再一次发挥了它"一等公民"的特权:函数声明提升比变量更高,所以这一问实际执行代码可看作:

function getName() {
    console.log(5);
};

var getName;

getName = function () {
    console.log(4);
};

两者声明共同提升之后,变量的赋值操作最后执行,所以调用 getName() 输出的结果是 4 。

3. Foo().getName()

和第一问相比看似只多了个括号,实际考察的内容完全不一样。

我们先复习一下 JS 中的运算符优先级,这是下来全部解题的基础,MDN 汇总表 -> 链接在这里。

首先成员访问运算从左到右执行,所以我们要先看 Foo() 函数做了什么,根据题目 a 处的定义:

function Foo () {
 getName = function () {
   console.log(1);
 }
 return this;
}

执行 Foo() 之后为 getName 赋值一个函数,注意这里的 getName 并没有 var 关键字,所以还考察了作用域链的知识点,JS 在遇到未声明的变量时会向上一级一层层查找,前面我们知道了变量声明会提升,在全局作用域下 getName 是已经被声明的了,所以执行 Foo() 的作用其实就是把全局的 getName 又赋值了新函数。

而 Foo() 本身返回了 this,所以这一问又变成了「this.getName() 输出什么?」。这里当然也就考察了 this 关键字 的知识点,只要记住:this 谁最后调用它那它就指向谁,这里的 this 没有改变过指向,所以是在全局下执行,也就是执行 getName(),执行结果是前面 Foo() 赋予的新函数,所以输出了 1 。

4. getName()

由于题目条件是顺序执行,所以这里经过了第三问之后全局 getName 已经被修改过了,在上一问已经解析完,这里毫无疑问执行输出是 1 。

5. new Foo.getName()

乍一看以为是要考察 new 关键字 了,其实并没有,它还是考察了上面提到的运算符优先级,根据优先级我们可以得出,Foo.getName() 是会先执行的,执行完只是输出了第一问的结果,再对其执行 new 没有意义,最后输出的还是 2 。

6. new Foo().getName()

这里开始考察 new 关键字 的概念,但我们还是要先说说这一问涉及的运算符优先级问题,可能你看过其它文章解析这一问的时候会说等价于 (new Foo()).getName(),可你知道为什么会是这样吗?为什么第 5 问是先执行 Foo.getName() 而这一问却是先执行 new Foo() 呢?

这是因为 new 运算在优先级上有两种形式,一种是带参数列表: new … ( … ) 优先级 18,另一种是无参数列表: new … 优先级 17,如果优先级不同那么按优先级最高的运算符先执行,不用考虑结合性(比如 1 + 1 * 2 执行起来就是 1 + (1 * 2)),如果优先级相同则按结合性执行(比如赋值运算结合性是"从右到左",所以 a = b = 1 实际为 a = (b = 1)),所以这就解释了为什么这一问会是 new Foo() 先执行,画个图就理解了:

在上一问里成员访问优先级是18,new(无参列表)优先级是17,优先级不同,则高优先级先执行,所以上一问先执行 Foo.getName();这一问里 new(带参列表)优先级与成员访问同属18,优先级相同,并行下看结合性,new 带参时结合性不相关,所以直接执行,成员访问结合性从左到右,所以先拿出 Foo() 执行,于是得出了上面等价于 (new Foo()).getName() 的结论。

接下来就是 new 的相关概念了,首先我们要知道 new 关键字做了什么:

  1. 创建新对象并将 .__proto__ 指向构造函数的 .prototype
  2. 将 this 指向新创建的对象
  3. 返回新对象

回到题目当中,new Foo() 以 Foo 为原型创建了一个新对象,这个实例本身并没有 geiName 这个方法,但是题目 c 处在 Foo 函数的原型上挂载过一个 getName 方法,最终实例会通过原型链访问到 Foo.prototype.getName() 这个方法,结果输出 3 。

原型链知识点:每个函数实例对象都有一个 __proto__ 属性,__proto__ 指向了 prototype,当访问实例对象的属性或方法,会先从自身构造函数中查找,如果找不到就通过 __proto__ 去原型中查找。

7. new new Foo().getName()

考察的上一问其实已经讲完了,还是一样画张图:

所以得出实际执行的是:new(new Foo().getName())

new Foo().getName() 在上一问可知实例最终访问 Foo 原型链上的方法,最终为创建 new(Foo.prototype.getName()) 的实例返回,结果输出 3 。

以上就是这道面试题的全部解析,应该是全网最详细的文章了,笔者一开始遇到这题时做错了好几遍,搞懂了以后发现这道题真的出得很好,考察的内容一环扣一环,如果看完对你有所帮助,还请不吝点赞支持一下

 给大家推荐一个实用面试题库

1、前端面试题库 (面试必备)            推荐:★★★★★

地址:前端面试题库

相关文章:

  • 神经网络(深度学习)----MLPClassifier库的初尝试
  • MindSpore Serving模型部署,如何提升吞吐量,降低推理时延
  • TCP/IP协议专栏——静态路由互导 详解——网络入门和工程维护必看
  • 你知道嵌入式开发主要做什么吗?
  • 树莓派电脑虚拟机3设备连接
  • 【软件测试】男生vs女生,谁更加适合?没有你发现不了的bug......
  • csv文件的读取和写入
  • mongoose之bulkWrite
  • 常用的设计模式
  • python 进程、线程、协程
  • 9月23日前,洪山区2022年智能制造与两化融合发展专项资金项目申报类型、条件
  • ML or DL
  • 2022年高教社杯国赛C题思路 : 古代玻璃制品的成分分析与鉴别
  • Linux ARM平台开发系列讲解(CAN) 2.14.2 CAN调试工具安装及其使用
  • 基JavaSwing开发公司管理系统+报告 课程设计 大作业
  • CAP理论的例子讲解
  • css的样式优先级
  • JAVA之继承和多态
  • leetcode讲解--894. All Possible Full Binary Trees
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • markdown编辑器简评
  • nginx 配置多 域名 + 多 https
  • React+TypeScript入门
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • VuePress 静态网站生成
  • windows下如何用phpstorm同步测试服务器
  • 模型微调
  • 通过几道题目学习二叉搜索树
  • 学习HTTP相关知识笔记
  • RDS-Mysql 物理备份恢复到本地数据库上
  • 阿里云API、SDK和CLI应用实践方案
  • 湖北分布式智能数据采集方法有哪些?
  • ​马来语翻译中文去哪比较好?
  • # centos7下FFmpeg环境部署记录
  • #图像处理
  • (1)(1.9) MSP (version 4.2)
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (DenseNet)Densely Connected Convolutional Networks--Gao Huang
  • (HAL库版)freeRTOS移植STMF103
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (附源码)php新闻发布平台 毕业设计 141646
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (图)IntelliTrace Tools 跟踪云端程序
  • .net Application的目录
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET delegate 委托 、 Event 事件
  • .NET 事件模型教程(二)
  • .Net程序帮助文档制作
  • .NET大文件上传知识整理
  • .NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .NET轻量级ORM组件Dapper葵花宝典
  • .NET与java的MVC模式(2):struts2核心工作流程与原理