引言
對於變量提升這個問題,我想從事前端的同學都或多或少認爲我懂這個。曾經,我也是這樣認爲的,我懂變量提升,並且可以從變量在 Chrome
中的內存分配講起,以及中間發生了什麼。
但是,在一次面試中,我遇到了幾個一起面前端的同學(當然技術水平參差不齊,並不是很高),在和他們聊這次筆試中的變量提升的問題時,發現大家都支支吾吾的,很多講的都是值的覆蓋。
當時的面試題是這樣的:
function fn(a) {
console.log(a)
var a = 2
function a() {}
console.log(a)
}
fn(1)
這個題目,最終會輸出function a(){}
和 2
。那麼,爲什麼是這個答案,這個過程發生了什麼很重要。所以,今天我們就來徹底刨析一下變量提升的過程。
一、變量在內存中的分配
在分析整個過程前,我們先來回顧一下 JavaScript
中變量在內存中的分配。
大家都知道的是,對於原始類型會存儲在棧空間中,對於引用類型會將引用存儲在棧空間中,將數據存儲在堆空間中。
其實這個過程還牽扯到函數上下文的創建,而每一個函數上下文中又會創建一個變量環境、詞法環境。有興趣的同學可以去看李斌老師的瀏覽器工作原理與實踐
所以,我們來看一個簡單的栗子,分析一下它在內存中的分配:
栗子:
var a = 1
var b = 2
var student = {name: 'wjc', age: 22}
它內存中的分配:
二、運行前的簡單編譯
衆所周知,JavaScript
是一門動態類型的語言,即它是在運行時確定變量的類型,不同於靜態類型語言的先編譯再運行的過程。但是,事實是 V8
引擎在解析運行 JavaScript
之前是會進行一次簡單的編譯,也就是我們通常所說的初始化過程。
這個初始化過程,會做這幾件事:
- 區分執行代碼和變量聲明代碼
- 變量聲明代碼劃分爲賦值代碼和初始化代碼
- 初始化代碼有兩種情況,一是對變量(原生類型、對象類型)初始化爲
undefined
;二是對函數的初始化,即直接指向函數在堆空間中的內存
那麼,我們就來看一個簡單的栗子:
console.log(a)
sayHi()
var a = 2
function sayHi() {
console.log('Hi')
}
那麼按照我們上面所說,這段代碼的賦值只有 var a = 2
,函數聲明只有進行編譯階段的代碼會是這樣的:
// 編譯代碼
var a = undefined
var sayHi = function () {
console.log('Hi')
}
此時,它在內存中的分佈:
然後,在執行階段的代碼會是這樣:
// 執行代碼
console.log(a)
sayHi()
a = 2
所以,也就是當我們真正執行的時候會走執行代碼,所以很顯然會輸出:
undefined
Hi
而當走完所有執行代碼後,此時內存是這樣的:
我想通過這個栗子,大家應該大致搞懂變量提升的過程。但是,仍然存在一個較爲特殊的情況,就是當函數形參存在時的變量提升,也就是我們文章開頭提及的面試題。
三、函數形參的編譯執行
首先,我們需要對函數調用做一個簡單的理解,在我們平常調用函數的時候,真正會經歷兩個步驟:
- 如果此時存在形參,則進行函數形參的編譯和執行過程
- 然後進入函數體,進行函數體內部的編譯和執行
可以看到這裏我們提到了當函數存在形參時,會先進行函數形參的編譯和執行過程。
這裏我們就來分析文章開頭這個栗子:
function fn(a) {
console.log(a)
var a = 2
function a() {}
console.log(a)
}
fn(1)
首先,此時是存在函數形參的,那麼函數形參的編譯和執行會是這樣:
var a = undefined
a = 1
然後,纔會進行函數體的編譯和執行:
// 編譯
a = function a() {} // 重點!!!
// 執行
console.log(a)
a = 2
console.log(a)
可以看到的是,如果函數體內的變量名和形參的變量名重複時,則不會進行普通變量的編譯賦值 undefined
的過程。但是,如果存在該變量是函數時,那麼則會進行函數變量的編譯賦值,即直接指向函數在堆空間中的地址。
所以,我們這個栗子在編譯後,可以看作是這樣的:
function fn() {
var a = undefined
a = 1
a = function a() {}
console.log(a)
a = 2
console.log(2)
}
很顯然,它會輸出會輸出function a(){}
和 2
寫在最後
不知大家在深度理解過變量提升過程後,是否有和我一樣的感受就是學習編程的本質是追溯本源。現今,雖然我們可以用 ES6
的 let
或 const
來聲明變量來避免 var
的種種缺陷。但是,如果因爲這樣而不去思考 var
爲什麼會存在這些缺陷。我想這是非常遺憾的。
寫作不易,如果你覺得有收穫的話,可以帥氣三連擊!!!