前言
此题目为几年前阿里首创,此题一出就制造了面试中"惨案"。该题有可能你能说出结果,但你未必能说清楚原因。
我查阅了部分关于此题的解析,好多就是迷迷糊糊就给讲完了,根本没抓住问题核心,因此阿包也献上一篇自己的理解,希望能为正确一方添加一票,这样大家搜索到正确频率又能提高一点。
下面是原版阿里真题和解析:
传送门: 阿里题目链接
题目
分析
该题目涉及到的知识非常多,比如: 作用域、预编译、原型与原型链、new
、事件循环。
我们首先来分析一下,该题上半部分到底做了些什么:
- 定义
test
函数 - 为
test
函数创建一个静态方法test.getName
- 为
test
函数创建一个实例方法test.prototype.getName
- 定义全局变量
getName
,程序运行后赋值为 函数 - 定义全局函数
getName
test
函数内部给全局变量getName
赋新值,返回值为this
与原题目相比,当前题目加入了事件循环的知识,
Promise.then
与setTimeout
都是异步任务,前者是微任务,后者为宏任务。
解析
预编译
- 函数预编译四部曲和全局预编译三部曲
- 变量声明,声明提升,函数声明,整体提升
GO: { test: fn, getName: function getName() { console.log(6); } } 复制代码
test.getName()
执行 test
函数上的静态方法 test.getName
,打印 3
,setTimeout
的回调插入事件队列中,等待同步任务执行完毕
getName()
getName
执行之前, getName
已经被重新赋值为 console.log(5)
,打印 5
test().getName()
test()
执行全局的test
函数,修改全局getName
值;test()
函数执行为默认绑定,非严格模式this
指向window
,返回值为window
GO: { test: fn, // 全局 getName 值修改 getName: function() { Promise.resolve().then(() => console.log(0)) console.log(1); }; } 复制代码
test().getName()
相当于执行window.getName()
,调用GO
中的getName
,Promise.then
压入微任务队列,打印1
getName()
执行全局函数 getName()
,打印 1
,Promise.then
压入微任务队列
(⭐)new 知识补充
在进行后面题目的解析时,先补充一点关于
new
运算符的知识。
首先咱们来看一下 MDN
中对 new
的描述,语法是:
new constructor[([arguments])] 复制代码
([arguments])
意味着可以缺省,会存在 new constructor(...args)
和 new constructor
两种模式,并且前者的运算优先级高于后者。更详细的优先级见下图:
从上图可以看到,部分优先级如下:new(带参数列表) = 成员访问 = 函数调用 > new(不带参数列表)
new test.getName()
根据上面的知识,表达式中自左向右共有三个运算符: new
不带参数列表、成员访问、函数调用。
优先级相同的运算符执行顺序自左向右,因此先执行成员访问,获取到 test
函数上的静态方法 getName
。
成员访问之后,表达式相当于变为 new (test.getName)()
,new
操作符由不带参数列表变为带参数列表,自左向右执行,把 test.getName
视为构造函数,生成对应实例。
new (test.getName)()
执行,打印 3
,setTimeout
回调压入宏任务队列。
new test().getName()
表达式运算符自左向右分别为: new
带参数列表、成员访问、函数调用
- 执行
new test()
:new
绑定,this
指向实例。 new
生成实例上没有getName
属性,沿原型链查找到test.prototype.getName
属性,打印4
new new test().getName()
表达式运算符自左向右分别为: new
不带参数、new
带参数、成员访问、函数调用
因此表达式可以转化为如下形式: new((new test()).getName())
new test().getName
: 访问到test.prototype.getName
属性new new test().getName()
: 以test.prototype.getName
为构造函数,打印4
Promise.then
执行微任务队列里的 promise.then
,微任务队列中共有两次 promise.then
,打印 2
个 0
setTimeout
执行宏任务队列里的 setTimeout
回调函数,打印 2
个 2
答案
3 5 1 1 3 4 4 0 0 2 2 复制代码
往期精彩文章
- 牛客最新前端JS笔试百题
- 抓取牛客最新前端面试题五百道 数据分析JS面试热点
- 原生JavaScript灵魂拷问(二),你能全部答对吗?
- 原生JavaScript灵魂拷问(一),你能答上多少?
- JavaScript之彻底理解原型与原型链
- JavaScript之预编译学习
- JavaScript之彻底理解EventLoop
- 《2w字大章 38道面试题》彻底理清JS中this指向问题
后语
如果大家感觉此文对你有一些帮助,希望能点个赞,鼓励一下阿包,阿包会不断努力的。另外如果本文章有问题,或者对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!