衆所周知,JS裏,變量有變量提升,函數有函數提升。ES6之前沒有塊級作用域。
然而,當用函數聲明的方法定義function並遇到非ES6情況下的{}時,卻有一些蜜汁行爲。
一道題引發的慘案
if (true) {
a = 1
function a() {}
// a = 1
console.log(a)
}
console.log(a)
if (true) {
// a = 1
function a() {}
a = 1
console.log(a)
}
console.log(a)
理想狀態下:if(){}
裏不存在let
、const
。應該不會產生Block
塊級作用域。
再根據函數提升,以上兩者相當於
function a() {}
a = 1
console.log(a)
console.log(a)
即輸出1
,1
實際情況卻是:
前者1
,1
後者1
,f a(){}
調試分析
debugger
調試分析問題
先將問題簡化下
debugger
console.log(a)
if (true) {
debugger
function a() {}
debugger
}
console.log(a)
輸出undefined
,f a(){}
第一個debugger
,js開始執行時,全局聲明瞭a但未定義。
第二個debugger
,a
的函數提升提升到塊級作用域Block
上,而非全局。
第三個debugger
,此處纔將Block
內的a
賦值給全局a
。
爲什麼是強調說是賦值而非直接在全局聲明呢。
現在將a=1
補上即再看一遍過程即可說明
debugger
console.log(a)
if (true) {
debugger
a = 1
debugger
function a() {}
debugger
}
console.log(a)
第三個debuger
時,1
會覆蓋掉Block
裏的f a(){}
。
第四個debuger
時,執行到function
的定義時,會發現全局a
變成了1
。
最後這一步執行的前後,很明顯可以看出,當執行當函數聲明語句時,js會將塊級裏的函數名屬性值賦給全局的函數名屬性,即window.a = block.a
。
實際執行情況約等於如下
var a;// 定義但未賦值,由function a(){}產生的。
if (true) {
let a = function a(){} // 函數提升到塊級
a = 1
window.a = a // 聲明語句時將塊級a賦值給全局a
console.log(a)
}
console.log(a)
個人理解總結
結論僅個人從結果推導,非查閱文檔所得,所以未必準確。
(非es6情況下)當{}
裏存在函數聲明(function a(){}
這種類型)時,js執行情況如下:
- js開始執行,函數名全局聲明但未定義
- 進入函數聲明的
{}
,產生臨時Block
,並且function
函數提升到Block
。 - 直到執行該
function
的定義語句時,纔會把Block
裏該函數名的屬性賦值給全局(哪怕改變了該屬性甚至類型),舉例:window.a = a
。