上次分享了一道题,你们反响不错,很开心本身写的东西有人愿意花时间去看,也给了本身莫大的鼓舞,其实作题虽然不比真正的编程,可是也可以让你发现一些你以前没有注意到的语言层面的问题。因此,此次再分享一道稍微有难度的JavaScript题目。javascript
function Foo() { getName = function () { console.log('1'); }; return this; } Foo.getName = function () { console.log('2'); }; Foo.prototype.getName = function () { console.log('3'); }; var getName = function () { console.log('4'); }; function getName() { console.log(5); } Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();
请问上述代码在浏览器环境下,输出结果是多少?
揭晓一下最终答案:java
2 4 1 1 2 3 3
前四道难度不是很大,主要是后三道,基本是全军覆没,感叹实在是太绕了了。后面慢慢分析了一下,逐个讲一下吧。
首先必须注意一个问题git
function Foo() { getName = function () { console.log('1'); }; return this; }
在函数内部声明的getName
变量,前面是不带有var
、let
,const
的,因此其实根据LHS(这个的介绍能够去的我博客看一下关于LHS和RHS的总结),声明的getName
是在全局范围内(也是就window
)。
其次须要明确你是否知道下面代码在浏览器中的执行结果:github
var getName = function () { console.log('4'); }; function getName() { console.log(5); } getName();
上述代码的执行结果是:4
。缘由是这样的,var
声明的变量和函数声明function
都会被提高,可是函数声明的提高的级别是比var
要高的,因此上面的代码的实际执行结果是:编程
function getName() { console.log(5); } var getName = function () { console.log('4'); }; getName();
后一个函数表达式getName
覆盖了前面的函数声明getName
,实际执行的是函数表达式(也就是是为何JavaScript永远不会有函数重载这么一说了),因此输出的是4
。
首先我给下面的代码添加一下必要的注释:浏览器
//函数声明 function Foo() { //全局变量 getName = function () { console.log('1'); }; return this; } //为函数添加属性getName,其类型是Function,因此这里也能够看出来,Function也是一种Object Foo.getName = function () { console.log('2'); }; //为Foo的原型添加方法getName Foo.prototype.getName = function () { console.log('3'); }; var getName = function () { console.log('4'); }; function getName() { console.log(5); }
下面执行第一条语句:函数
Foo.getName();
函数Foo
自己并无执行,执行的是函数的属性getName
,固然输出的是:2
.
接下来执行:this
getName();
这是在全局范围内执行了getName()
,有两条对应的getName
的声明,根据前面咱们所提到的提高的级别来看实际执行是函数表达式:spa
var getName = function () { console.log('4'); };
因此输出的是4
。
接下来执行prototype
Foo().getName();
首先看一下JavaScript的操做符优先级,从高到低排序
从上面能够看出来()
与.
优先级相同,因此Foo().getName()
从左至右执行。首先运行Foo()
,全局的getName
被覆盖成输出console.log('1')
,而且返回的this
此时表明的是window
。随后至关于执行的window.getName()
,那么输出的实际就是1
(被覆盖)。
下面到了
getName();
这个不用说了,执行的仍是:1
(和上面一毛同样)。
下面到了三个最难的部分:
new Foo.getName();
对于这条语句的执行,有两种可能:
(new Foo).getName()
或
new (Foo.getName)()
可是咱们根据操做符优先级表能够得知,其实上.
操做符要比new
优先级要高,因此实际执行的是第二种,因此是对
Foo.getName = function () { console.log('2'); };
函数执行了new
操做,固然输出的是2
。
下面到了执行
new Foo().getName();
这个语句的可能性也有两种:
(new Foo()).getName();
或者
new (Foo().getName)();
那么应该是那种的呢?原来我觉得会是第二种的执行方式,后面经过浏览器调试发现真实的执行的方式是第一种。我看到题目的做者是这么解释的:
首先看运算符优先级括号高于new。实际执行为(new Foo()).getName()。遂先执行Foo函数。
我以为上面的解释是有问题的,对比上面两种执行方式,第一种是先执行new
,而后执行的是.
操做符,而后执行的是()
。第二种是先执行了()
,再执行的是.
,最后执行new
操做符。若是真的按照引用所说的用优先级的方式判别,其实偏偏应该执行的是第二种而不是第一种。
后来总算找到缘由了,原来以前那个出现的比较多的JavaScript优先级的表并不完整,万能的MDN给出了最权威的JavaScript优先级表运算符优先级
我列举出最重要的部分(由高到低):
因此带参数的new
操做符是优先级最高的,这下就没有问题了,执行顺序确实应该是第一种。
那么按照(new Foo()).getName();
来执行,状况就就很简单了,(new Foo())
返回了新生成的对象,该对象没有getName()
方法,因此在prototype
中找到了getName()
方法。因此输出的是3
。
胜利就在眼前,咱们看一下最后一问。
new new Foo().getName();
和上一步同样的方法,咱们按照优先级表给分析一下这个语句究竟是怎么执行的。
首先带参数的new
操做符优先级最高,第一步划分为:
new (new Foo().getName)();
第二步划分为:
new ((new Foo()).getName)();
因此执行(new Foo()).getName
这个函数是对应的Foo.prototype.getName,因此执行new (Foo.prototype.getName)()
确定输出的是3
。
哈哈哈,这么可贵题终于解决了,开心~总结一下吧,首先JavaScript知识最好去MDN去查,万一别的地方写错了真的是贻害不浅。其次,若是在写代码的时候仍是少利用操做符优先级这种东西,一旦不明确的地方就马上用()
,代码的可阅读性真的是很重要!很重要!很重要!毕竟代码仍是给人看~
若是有写的不正确的地方,欢迎你们指出,资历深浅,请多指教。欢迎你们去围观个人博客呀~~http://mrerhu.github.io