前言
此题目为几年前阿里首创,此题一出就制造了面试中"惨案"。该题有可能你能说出结果,但你未必能说清楚原因。
我查阅了部分关于此题的解析,好多就是迷迷糊糊就给讲完了,根本没抓住问题核心,因此阿包也献上一篇自己的理解,希望能为正确一方添加一票,这样大家搜索到正确频率又能提高一点。
下面是原版阿里真题和解析:
传送门: 阿里题目链接
题目
分析
该题目涉及到的知识非常多,比如: 作用域、预编译、原型与原型链、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指向问题
后语
如果大家感觉此文对你有一些帮助,希望能点个赞,鼓励一下阿包,阿包会不断努力的。另外如果本文章有问题,或者对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!
