1. 程式人生 > >js中變數名與函式名重名的問題

js中變數名與函式名重名的問題

網上有很多的部落格有談到這個重名的問題,但是我覺得將不算是很全面,至少我看了還是不懂下面這道題到底是怎麼回事。那在講解這個問題的時候,我們先來看看下面這道題,一道面試題(南山西麗xx研究院):

var a = 1;
function b(){
    a = 10;
    return;
    function a(){
        console.log(a);
    }	
}
b();
console.log(a); 

這題打印出a的值為多少呢?可能會有很多的同學認為打印出的值為10,但其實並不是,為什麼呢?

誤區1:變數提升

我想大家都明白,在執行函式b的時候,由於其內部有一句a=10,前面並沒有var,所以在執行完函式b之後,認為變數a提升為全域性變數,並且10這個值覆蓋了之前的1。所以打印出的值為10。我想的話這是多數新手的思路。

這裡有個知識點:

顯示宣告:var a = 1;

隱式宣告:a = 1;

隱式宣告會變數提升為全域性變數

誤區2:不知道函式宣告也是有提升的

多數新手認為在執行函式b的時候,js執行程式碼自上往下,執行到return的時候退出函式,不再執行函式a,所以認為這個函式a根本不起作用。

js知識盲區:js預解析與解析變數宣告的順序

js預解析:在執行程式碼之前,js會預解析程式碼,程式碼中如果有變數宣告和函式宣告的話,那麼便將變數宣告和函式宣告置頂,網上多數人認為函式宣告置頂比變數宣告置頂更優先。(事實上是如此嗎?這裡先埋一個坑,稍後再來討論)

js解析宣告變數的順序:js在解析變數宣告的時候,是分為兩個步驟的

比如這句程式碼:var a = 1;其實是分為(1)var a;(2)a=1;兩個解析步驟來進行的。

好,講完兩個知識點,那麼我將原始碼改寫成如下形式:

function b(){
    function a(){    //函式宣告置頂
        console.log(a);
    }
    a = 10;
    return;	
}
var a;
a  = 1;
b();
console.log(a);

那麼大家再來看看這段程式碼,覺得你們心目中的答案是多少呢?若是還是覺得等於10的話,那麼就說明你們還沒有考慮到本文要講的重點:重名問題!

在講解這段程式碼前,我還要給大家要普及一個知識點:作用域鏈(若是有不懂的同學,可以查閱連結:

js閉包)。

好,懂作用域鏈的同學應該明白,在呼叫變數或者一個物件屬性等的時候,查詢的順序是由裡向外的,js執行程式碼是自上往下的。

那麼我們再來分析剛才修改過後的程式碼:

1)先宣告變數a,系統給變數a分配一個記憶體,注意,這裡變數a暫時還不知道它的變數型別,因為還沒有賦值;

2)a=1,給變數賦值為1,變數型別為number;

3)開始執行函式b,函式b內有一個函式名為a的函式宣告,且函式a下面有一個變數名為a的變數,且賦值為10,這裡便開始了難點分析。首先看函式a,由於在執行函式b的時候,並沒有呼叫函式a,因此函式a並沒有起作用;但是執行到a=10的時候,js是這樣做的:1.首先查詢變數a的地址,從系統記憶體中開始按照作用域鏈查詢;2.由於作用域鏈的查詢順序是由裡向外的,故要先從函式b裡面開始查詢;3.在查詢的過程中,發現函式b中已經聲明瞭一個函式名為a的函式(重名問題!),所以查詢到函式名為a的函式後,這裡便不再往外查詢;4.所以這裡的a=10其實是將10賦值給了函式名為a的這個函式物件!

有的同學會問為什麼了。這樣說吧,在函式b裡面的時候,函式a提升置頂,跟變數類似(都在js中聲明瞭),Js給這個函式a分配了記憶體,而恰巧的是在執行a=10的時候,優先查詢的是與變數a同名的函式a,因為它所在的作用域最近!講到這裡,我想絕大多數的同學已經明白了這題為什麼會列印1了吧?

因為既然a=10這個值10賦值給了函式物件a,那麼在全域性環境下執行console.log(a),訪問的是全域性變數a,而全域性變數a在系統記憶體中查詢的值為1,所以列印的值為1.

那麼同學們可以自行將函式b中的函式a給註釋掉,看看列印的結果是什麼?看程式碼:

function b(){
    a = 10;
    return;	
}
var a;
a  = 1;
b();
console.log(a);

答案很明顯是10,為什麼?大多數同學都知道的吧,是的,這裡在函式b裡的作用域裡沒有查詢到名為a的物件或者變數,那麼繼續向外查詢,發現在全域性作用域裡發現了有變數a的記憶體地址,於是將10這個值賦值給了全域性變數a。

懂了?好,我們再將程式碼再修改一次,如下:

function b(){
    a = 10;
    return;	
}
b();
console.log(a);

這個很簡單的吧?列印的值為多少呢?依舊是10對吧,為什麼呢?是這樣的,在執行函式b的時候,發現有一個隱式宣告a=10,它在函式b裡的作用域中查詢不到有關於名為a的地址,於是向外查詢,發現全域性作用域下也沒有,那麼變數提升,系統預設給變數a提供一個記憶體,並將值賦值給變數a,所以變數a變數提升為全域性變量了。所以打印出的值為10.

好,講完這個,我們繼續看看我剛才埋下的坑,現在大家來執行這兩段程式碼:

程式碼1:

var a;
function a(){  
  console.log(10);  
}  
console.log(a);

程式碼2:

function a(){  
  console.log(10);  
}  
var a;
console.log(a);

執行之後我們會發現,兩段程式碼執行的結果是一樣的,均為:function a(){console.log(10)}。

這就應證了網上的說法:函式宣告置頂比變數宣告置頂更優先。如果不是的話,那麼第二段程式碼應該列印undefined,可是為什麼打印出的卻是函式a呢?這個例子是否告訴我們變數宣告置頂比函式宣告置頂更優先(這個是我同學研究出來的)?但是總歸一句話,變數賦值肯定是在兩個宣告的下面的,所以這並不妨礙我們編寫程式碼,或者做面試題。當然如果剛好出了這麼一道題,我希望大家把它當特殊例子對待吧。

變數名與函式名重名的總結:

1.要知道js解析變數宣告的順序

2.函式宣告和變數宣告會置頂且函式宣告更優先的說法不對!應該是變數宣告比函式宣告更優先!

3.作用域鏈的查詢順序是由裡向外,js執行程式碼順序是自上往下