1. 程式人生 > >一道面試題的解析

一道面試題的解析

原題

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) }

Foo.getName()  //???
getName()  //???
Foo().getName()  //???
getName()  //???
new Foo.getName() //??? new Foo().getName() //??? new new Foo().getName() //???

  答案你可以輕易得到,因為有控制檯輸出。

解析

  首先你需要明白的是,寫了這麼多,現在一共有幾個變數。變數提升我相信大家應該都清楚的吧,在這裡我們可以把上邊的程式碼分成宣告部分和賦值部分如下:

//宣告部分
var getName
function Foo(){
  getName = function(){ alert(1) }
  return this
}
function getName(){ alert(5) }
//賦值部分
Foo.getName = function(){ alert(2) } Foo.prototype.getName = function(){ alert(3) } getName = function(){ alert(4) }

  這段程式碼最終得到的是什麼呢?
  一個全域性變數getName,一個函式Foo。其中這個函式裡邊有一個變數getName,變數的值是一個匿名函式function(){ alert(2) },除此之外這個函式的原型鏈上也有一個變數getName,它的值是一個匿名函式function(){ alert(3) }。這裡需要注意的是函式Foo宣告的那段程式碼其實並沒有在函式內部建立一個getName變數,這裡做的實際上是把全域性的getName重新賦值。
  或者如果你沒有看明白大可以給它換一個變數名,比如下邊的:

var getA
function Foo(){
  getA = function(){ alert(1) }
  return this
}
function getA(){ alert(5) }
//賦值部分
Foo.getB = function(){ alert(2) }
Foo.prototype.getC = function(){ alert(3) }
getA = function(){ alert(4) }

  在這一步你需要讀懂的是,你在修改的到底是哪個變數。

答案

  在真正分析答案的時候你需要知道的是運算子的優先順序。
1. Foo.getName()
  此時呼叫的是Foo內部的getName函式,也就是我說的getB,由上邊的程式碼你可以輕易看出結果是alert(2)
2. getName()
  呼叫的是全域性的getName,也就是getA,最終值是alert(4)
3. Foo().getName()
  此句可以拆分為

var a = Foo()  
a.getName()

  第一句執行了全域性變數getName的賦值(getA),所以此時getName = function(){ alert(1) }。同時函式Foo返回的是this,此時this指的是全域性物件window。
  第二句實際上是window.getName(),我們知道,此時呼叫的是全域性的getName,也就是getA。因此結果是alert(1)
4. getName()
  呼叫的是全域性的getName,所以結果也是alert(1)
5. new Foo.getName()
  拆分為

var a = Foo.getName()
new a 

  其中第一句執行了Foo.getName()這個函式,此時已經有了執行結果alert(2),第二句的new a實際上只是使用這個函式構造了一個物件,並不產生實際的執行效果。如果你在Foo.getName這個函式的prototype上掛一個變數getD,比如

Foo.getName.prototype.getD = function(){ alert(6) }

  那麼你執行new Foo.getName().getD()得到的會是alert(2)alert(6)
6. new Foo().getName()
  拆分為

var a = new Foo()
a.getName()

  這個的疑問可能會集中在先運算哪一個的上邊,你可以查一查運算子的優先順序。new Foo().getName()的優先順序是相同的,所以按順序來。在上一題中,new Foo.getName()優先順序低,所以先執行.getName()
  new Foo()得到的是Foo構造出來的物件,Foo新構造出來的物件並沒有getName方法,所以在後邊呼叫getName方法的時候會向它的原型鏈上查詢,所以此時呼叫的是原型鏈上的getName函式,所以結果是alert(3)
7. new new Foo().getName()
  拆分為

var a = new Foo()
var b = a.getName()
new b

  依舊是運算子的優先順序問題,new aa.getName()的優先順序低,所以先執行的是a.getName(),此時已經得到了執行結果,第三句的new b只是構造了一個函式,並沒有產生實際的效果。所以這個的結果也是alert(3)