1. 程式人生 > >每日進步一點點(2018.11.14)

每日進步一點點(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.