每日進步一點點(2018.11.14)
今日討論如下:
function Fn(){
getName = function (){ alert(1);};
return this;
}
Fn.getName =function (){ alert(2);};
Fn.portotype.getName = function (){ alert(3);};
var getName = function (){ alert(4);};
function getName(){ alert(5);};
//請寫出以下輸出結果:
Fn.getName();
getName();
Fn().getName();
getName();
new Fn.getName();
new Fn().getName();
new new Fn().getName();
此題涉及的知識點眾多,包括變數定義提升、this指標指向、運算子優先順序、原型、繼承、全域性變數汙染、物件屬性及原型屬性優先順序等等。
此題包含7小問,分別說下:
第一問
先看此題的上半部分做了什麼,首先定義了一個叫Fn的函式,之後為Fn建立了一個叫getName的靜態屬性儲存了一個匿名函式,之後為Fn的原型物件新建立了一個叫getName的匿名函式。之後又通過函式變量表達式建立了一個getName的函式,最後再宣告一個叫getName函式。
第一問的Fn.getName自然是訪問Fn函式上儲存的靜態屬性,自然是2,沒什麼可說的。
第二問
第二問,直接呼叫getName函式。既然是直接呼叫,那麼就是訪問當前上文作用域內的叫getName的函式,所以1 2 3都沒什麼關係。此題有無數面試者回答為5。此處有兩個坑,一是變數宣告提升,二是函式表示式。
變數宣告提升:
即所有宣告變數或者宣告函式都會被提升到當前函式的頂部。
例如下程式碼:
console.log('a' in window);//true
var a;
a=0;
程式碼執行時js引擎會將宣告語句提升至程式碼最上方,變為:
var a;
console.log('a' in window);//true
a=0;
函式表示式:
var getName與function getName都是宣告語句,區別在於var getName是函式表示式,而function getName是函式宣告。關於JS中的各種函式建立方式可以看 大部分人都會做錯的經典JS閉包面試題 這篇文章有詳細說明。
函式表示式最大的問題,在於js會將此程式碼分拆為兩行程式碼分別執行。
例如下程式碼:
console.log(a);//輸出:function a(){}
var a=1;
function a(){}
實際執行的程式碼為,先將var a=1拆分為var a;和a=1;兩行,再將var a;和function a(){}兩行提升至最上方變成:
var a;
function a(){}
console.log(a);
x=1;
所以最終函式宣告的a覆蓋了變數宣告的a,log輸出為a函式。
同理,原題中程式碼最終執行時的是:
function Fn(){
getName = function (){alert(1);};
return this;
}
var getName;//只提升變數宣告
function getName(){alert(5);};//提升函式宣告,覆蓋var的宣告
Fn.getName = function (){alert(2);};
Fn.prototype.getName = function(){alert(3);};
getName = function(){alert(4);};//最終的賦值再次覆蓋function getName宣告
getName();//最終輸出4
第三問
第三問的Fn().getName();先執行了Fn函式,然後呼叫Fn函式的返回值物件的getName屬性函式。
Fn函式的第一句getName = function(){alert(1);};是一句函式賦值語句,注意它沒有var宣告,所以先向當前Fn函式作用域內尋找getName變數,沒有。再向當前函式作用域上層,即外層作用域內尋找是否含有getName變數,找到了,也就是第二問中的alert(4)函式,將此變數的值賦值為function(){alert(1)}。
此處實際上是將外層作用域內的getName函式修改了。
注意:此處若依然沒有找到會一直向上查詢到window物件,若window物件中也沒有getName屬性,就在window物件中建立一個getName變數。
之後Fn函式的返回值是this,而JS的this問題部落格園中已經有非常多的文章介紹,這裡不再多說。
簡單地講,this的指向是由所在函式的呼叫方式決定的。而此處的直接呼叫方式,this指向window物件。
遂Fn函式返回的是window物件,相當於執行window.getName(),而window中的getName已經被修改為alert(1),所以最終會輸出1.
此處考察了兩個知識點,一個是變數作用域問題,一個是this指向問題。
第四問
直接呼叫getName函式,相當於window.getName(),因為這個變數已經被Fn函式執行時修改了,遂結果與第三問相同,為1.
第五問
new Fn.getName();,此處考察的是js的運算子優先順序問題。實際上將getName函式作為了建構函式來執行,遂彈出2.
第六問
new Fn().getName();,首先看運算子優先順序括號高於new,實際執行為(new Fn()).getName();
遂先執行Fn函式,而Fn此時作為建構函式卻有返回值,原題中,返回的是this,而this在建構函式中本來就代表當前例項化物件,遂最終Fn函式返回例項化物件。
之後呼叫例項化物件的getName函式,因為在Fn建構函式中沒有為例項化物件新增任何屬性,遂到當前物件的原型物件(prototype)中尋找getName,找到了。
遂最終輸出3.
第七問
new new Fn().getName();,同樣是運算子優先順序問題。
最終實際執行為:
new (new Fn()).getName();
先初始化Fn的例項化物件,然後將其原型上的getName函式作為建構函式再次new。
遂最終結果為3.