今天做到的一道题,还是蛮有意思的,巩固了一下函数声明这一块的知识点,其中包含了函数表达式、声明提升以及优先级问题。
先看下面这几行代码声明:
1 | function Foo () { |
再看看以下执行的输出结果:
1 | Foo.getName(); //2 |
为什么是这几个结果,下面逐一分析一下:
①输出2,我们看到第一行的Foo
是一个函数声明,里面看似有一个getName
函数声明,然而它并没有var
,它其实是会从作用域链逐层往外去找,真正声明的是Foo.getName = function(){ alert(2) }这一段。
所以调用该函数工厂方法,输出2。
②输出4,①中我们提到过Foo
中这个getName
在全局中去找是否有这个声明,结果发现没有就在全局有了一个getName = function () { alert(1) }
,在这之后又有一个var getName = function () { alert(4) }
以及function getName () { alert(5) }
,看似都是在全局作用域作了声明,但这里有一个函数表达式与声明提升的问题:前者是函数表达式,而函数表达式在解析的时候会拆成两部分,第一部分是var getName;
声明,此时这部分会与后面的function getName() { alert(5) }
一起提升到最前面,这个时候还是输出5,把前面的1覆盖了,但是第二部分就是getName = function() { alert(4) }
,所以最后覆盖成输出4。
③第一反应是输出1,因为Foo()
再次进行全局声明,将前面的输出4覆盖。其实这样理解也没啥问题,但是还有一个return this;
,这个this由调用方决定,此时自然是window
对象,而前面的由于没有var
的声明,也是挂在全局上。
④直接调用,作用环境也是window
,本质上与③相同,输出1。
⑤这里是优先级问题,即.
运算符的优先级要高于new
,所以执行等价于new(Foo.getName)()
,相当于将Foo.getName
作为构造函数执行,固输出2。
⑥与⑤的差异其实就是这个()
,而这个()
恰好和.
是同级的,那就按从左到右运算解析,即(new Foo()).getName()
,输出实例对象的getName
函数,而构造函数Foo
中并未对实例化对象添加该描述,所以顺着原型链往上找,有Foo.prototype.getName = function () { alert(3) }
,输出3。当然这里面还有个知识点就是构造函数返回值的问题:当没有返回值或者返回值是基本类型如(string,number,boolean,null,undefined,symbol)这些,实际上返回的就是实例化对象,若是引用类型,则结果就是这个引用类型。
⑦同样是优先级问题,实际执行顺序为new ((new Foo()).getName)();
,先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new,所以还是输出3。