JavaScript中變量名與函數名重名的問題


轉載一位大神關於變量名與函數名重名問題:

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呢?這個例子是否告訴我們變量聲明置頂比函數聲明置頂更優先?

但很明確的告知大家,函數聲明置頂比變量聲明置頂更優先。但上述的結果不是按照聲明提升的思維來看,是因為在預解析階段變量只聲明了,但未賦值,而函數不一樣,它在預解析階段就已經聲明和賦值了,它的值就是函數這個對象,而在打印的時候,打印是需要出具體的值的,而變量還未賦值,函數卻已賦值了,所以兩次運行結果均是function a(){console.log(10)}。如果去掉函數a,運行的結果是undefined,是因為a被初始化並賦值為undefined了。

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

1.要知道js解析變量聲明的順序

2.函數聲明和變量聲明會置頂且函數聲明更優先!

3.作用域鏈的查找順序是由里向外,js執行代碼順序是自上往下
————————————————
版權聲明:本文為CSDN博主「Charles_Tian」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/charles_tian/article/details/79775909


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM