当前位置: 首页 > Web前端 > HTML

这道经典的JS面试题不用死记硬背,今天带你彻底吃透!

时间:2023-03-28 01:41:04 HTML

前言这是一道非常经典的面试题,涵盖了函数的基本概念、运算符优先级、作用域链、原型链、this关键字、new关键字等基础知识点。可以说能把JS的基础完全答对才算及格。本文将带你回顾分析这道面试题。应该是全网最详细的文章了。这一次,我会彻底明白的。//afunctionFoo(){getName=function(){console.log(1);}returnthis;}//bFoo.getName=function(){console.log(2);}//cFoo.prototype.getName=function(){console.log(3);}//dvargetName=function(){console.log(4);}//efunctiongetName(){console.log(5);}按顺序执行后分别输出什么?Foo.getName();getName();Foo().getName();getName();newFoo.getName();newFoo().getName();newnewFoo().getName();试着写出结果再看答案,再详细分析。回答Foo.getName();//2getName();//4Foo().getName();//1getName();//1新的Foo.getName();//2newFoo().getName();//3newnewFoo().getName();//3分析1.Foo.getName()这道题首先考察了函数的基本概念:在JS中,函数是一等对象,也被称为“一等公民”,因为函数具有对象的所有功能。所以这里的Foo.getName()可以看作是调用了Foo对象上的属性,也就是标题中b处定义的,所以结果输出为2。2、getName()调用的getName在上下文中定义了两次,一次是通过变量声明,一次是函数声明。所以本题考查变量声明和函数声明的提升,声明会提前提升。到代码顶层,函数再次发挥“一等公民”特权:函数声明的提升高于变量声明,所以本题实际执行代码可以看成:函数getName(){console.log(5);};vargetName;getName=function(){console.log(4);};两个声明一起提升后,最后执行变量赋值操作,所以调用getName()的输出结果为4。3.与第一题相比,Foo().getName()好像只多了一个括号,但调查的实际内容完全不同。先回顾一下JS中的运算符优先级,它是一切解题的基础。->MDN-运算符优先级汇总表。首先,成员访问操作是从左到右进行的,所以我们需要先看看Foo()函数做了什么,根据题目a中的定义:functionFoo(){getName=function(){console.log(1);}returnthis;}执行完Foo()后,给getName赋值一个函数(注意这里getName中没有var关键字,所以我们也考察作用域链的知识点),JS遇到undeclared会上一层variables层层查找,知道变量声明会被提升。在全局范围内,已经声明了getName,所以执行Foo()的作用实际上是将全局的getName赋值给一个新的函数。而Foo()本身返回这个,所以问题变成了“this.getName()输出什么?”。当然这里也考查了这个关键字的知识点。在这里,它是在全局上下文中执行的。this指的是window,也就是调用了getName()。执行结果是之前Foo()给出的新函数,所以输出1。4.getName()由于题目条件是顺序执行的,所以这里第三题之后修改了全局的getName,前面的题也解析过了,所以这里的执行输出毫无疑问是1。5.newFoo.getName()乍一看以为是检查new关键字,其实不是。它还审查了上述运营商的优先权。根据优先级,我们可以得出Foo.getName()会先被执行。执行完后只输出第一题的结果,在上面执行new是没有意义的。最后的输出还是2。6.newFoo().getName()这里开始考察new关键字的概念,不过还是要说一下这道题涉及到的运算符的优先级。也许你看了其他文章就会明白这个问题。说是等同于(newFoo()).getName(),但是你知道为什么会这样吗?为什么第5题Foo.getName()先执行,而本题newFoo()先执行?这是因为new操作有两种形式的优先级:有参数列表:new...(...)优先级18无参数列表:new...优先级17如果优先级不同,优先级最高的运算符将先被执行,不要考虑结合性。例如,1+1*2执行为1+(1*2)。如果优先级相同,则按照结合律执行。比如赋值运算的结合性是“从右到左”,所以a=b=1其实就是a=(b=1)所以这就解释了为什么这道题会先执行newFoo()。画个图看看:上一题中memberaccess的优先级是18,new(无参数列表)的优先级是17,优先级不同,优先级高的会先执行,所以上一题首先执行Foo.getName();而这道题new(有参数列表)和memberaccess的优先级都是18,优先级是一样的,我们并行看关联性,new带参数的时候关联性是无关的,所以直接执行,而成员访问的结合性是从左到右,所以先取出Foo()再执行,所以上面等价于(newFoo()).getName()的结论。下一步是检查与新相关的概念。newFoo()创建一个以Foo为原型的新对象。这个实例本身没有geiName方法,但是主体c在Foo函数的原型上挂载了一个getName方法,最后这个实例会通过原型链访问Foo.prototype.getName()方法,结果将输出3。回顾一下new关键字的作用:创建一个新对象,将.__proto__指向构造函数的.prototype将this指向新创建的对象返回新对象回顾原型链的知识点:每个函数实例对象都有一个__proto__属性,__proto__指向原型,在访问实例对象的属性或方法时,会先从自身的构造函数中查找,如果找不到,则通过__proto__在原型中查找。7.newnewFoo().getName()上一题其实已经讲完了,画个图还是一样:所以得出结论,实际执行是:new(newFoo().getName())newFoo().getName()在上一题中我们可以看到实例最终访问了Foo原型链上的方法,最终返回了new(Foo.prototype.getName())创建的实例,以及结果输出3以上就是文章的全部内容,希望对你有所帮助!觉得文章写得好,可以点赞收藏。也欢迎您关注。我会在前端持续更新更多有用的知识和实用技巧。一日茶无味,愿与你共同成长~