轉載一位大神關於變量名與函數名重名問題:
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
