new Foo().getName();
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
关键字做了什么:
- 创建新对象并将
.__proto__
指向构造函数的.prototype
- 将
this
指向新创建的对象- 返回新对象
回到题目当中,new Foo()
以 Foo
为原型创建了一个新对象,这个实例本身并没有 geiName
这个方法,但是题目 c 处在 Foo
函数的原型上挂载过一个 getName
方法,最终实例会通过原型链访问到 Foo.prototype.getName()
这个方法,结果输出 3 。
专业技能
一般来说,面试官会根据你的简历内容去提问,但是技术基础还有需要自己去准备分类,形成自己的知识体系的。简单列一下我自己遇到的一些题
- HTML+CSS
- JavaScript
- 前端框架
- 前端性能优化
- 前端监控
- 模块化+项目构建
- 代码管理
- 信息安全
- 网络协议
- 浏览器
- 算法与数据结构
- 团队管理
- 开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
最近得空把之前遇到的面试题做了一个整理,包括我本人自己去面试遇到的,还有其他人员去面试遇到的,还有网上刷到的,我都统一的整理了一下,希望对大家有用。
其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等
由于文章篇幅有限,仅展示部分内容