Fork me on GitHub

函数表达式与声明提升

 今天做到的一道题,还是蛮有意思的,巩固了一下函数声明这一块的知识点,其中包含了函数表达式、声明提升以及优先级问题。

  先看下面这几行代码声明:

1
2
3
4
5
6
7
8
function Foo () {
getName = function () { alert(1) }
return this
}
Foo.getName = function () { alert(2) }
Foo.prototype.getName = function () { alert(3) }
var getName = function () { alert(4) }
function getName () { alert(5) }

  再看看以下执行的输出结果:

1
2
3
4
5
6
7
Foo.getName(); //2
getName(); //4
Foo().getName(); //1
getName();//1
new Foo.getName(); //2
new Foo().getName(); //3
new new Foo().getName(); //3

  为什么是这几个结果,下面逐一分析一下:
  ①输出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。