javascript 變數提升 函式有嗎 怎麼用?
1.JS程式碼執行順序
我們直覺上會認為JS的程式碼在執行時是由上到下一行一行執行的,但實際並不完全正確,下面的例子會證明:
a = 'haha'
var a
console.log(a)
上面的程式碼會輸出什麼呢?
如果按照我們認為的由上到下一行一行執行,那麼應該輸出undefined
,但是實際結果是'haha'
。
接著再看一個程式碼:
console.log(a)
var a = 'haha'
那這個輸出的是什麼?
鑑於上面程式碼表現出來的非自上而下的特點,有可能認為是’haha’。或者有認為變數a沒有宣告,所以會報錯,但實際結果是undefined
為什麼會是這樣呢?到底發生什麼?讓我們帶著這個問題看下去
2.變數提升
2.1 編譯
我們要知道,引擎在解釋JS程式碼之前首先要對程式碼進行編譯,在編譯階段中有一部分工作就是找到所有的宣告,並用合適的作用域將他們關聯起來。
JS執行的流程圖大致如下:
所以總結來說,包括變數和函式在內的所有宣告在編譯階段都會首先被處理,然後才是程式碼被執行的階段。
當我們看到var a = 'haha'
時候認為它是一個宣告,但其實在JS中它會被看做兩個宣告,var a
和a = 'haha'
。var a
是定義宣告,是在編譯階段進行;a = 'haha'
是賦值宣告,會留在原地等待執行階段。
2.2 解釋上面問題
接著我們再看回第一個例子:
a = 'haha'
var a
console.log(a)
通過上面說明,可以知道會先處理定義宣告,所以整個程式碼會以如下形式進行處理:
var a //在編譯階段進行變數提升
a = 'haha'
console.log(a)
第二個例子也是相同處理
console.log(a)
var a = 'haha'
在這個例子中也會進行變數提升,所以整個程式碼會以如下形式進行處理:
var a
console.log(a)
a = 'haha'
3.函式提升
3.1 基礎用法
看完變數提升,再看一下函式宣告如何進行提升
foo()
function foo() {
console.log('haha')
}
看完上面的例子,我想大家也能猜到了結果,就是’haha’。
因為foo函式的宣告被提升了,所以第一行中的呼叫可以正常執行。
3.2 作用域提升
接下來再看一個例子
foo()
function foo() {
console.log(a)
var a = 'haha'
}
這個會輸出什麼呢?
答案是:undefined
在這個例子中,不只有函式提升,還有變數提升。要注意的是,每個作用域都會進行提升操作,所以foo()函式自身也會在內部對var a進行提升,但是隻能在這個作用域中進行提升,並不能提升到整個程式碼的最上方。
因此這段程式碼會以如下形式進行處理:
function foo() {
var a
console.log(a)
a = 'haha'
}
foo()
3.3 函式表示式
foo()
var foo = function bar() {
console.log('haha')
}
這個程式碼執行結果是:TypeError: foo is not a function
這是因為變數標識foo被提升並分配給所在作用域,所以不會出現ReferenceError的錯誤,但是foo此時沒有賦值,也就是此時foo是undefined,所以執行foo()是對undefined值進行函式呼叫,因此結果為TypeError。
這個例子證明了:函式宣告會被提升,但是函式表示式不會被提升
3.4 具名函式表示式
foo()
bar()
var foo = function bar() {
console.log('haha')
}
從上面我們知道,執行foo()會是typeError,那執行bara()呢?
答案是:ReferenceError: bar is not defined
這是因為:即使是具名的函式表示式,名稱識別符號在賦值之前也無法在所在作用域中使用。
因此這段程式碼會以如下形式進行處理:
var foo
foo() //typeError
bar() //ReferenceError
foo = function bar() {
console.log('haha')
}
4. 函式優先
函式宣告和變數宣告都會提升,但是在處理宣告中是函式首先被提升,然後才是變數。
foo()
var foo
function foo() {
console.log(1)
}
foo = function () {
console.log(2)
}
這個例子的結果是1。
這個程式碼會以近似如下形式進行處理:
function foo() {
console.log(1)
}
foo()
foo = function () {
console.log(2)
}
注意:var foo
雖然會出現在foo()之前,但是因為重複的宣告,所以被忽略了。
5.實戰練習
經過上面說明,應該對變數提升有了大致瞭解,那麼讓我們再看幾個例子,看看是否真正理解
5.1 例子1:
var getName = function () {
console.log('4');
};
function getName() {
console.log(5);
}
getName();
想想這個結果是什麼?
讓我們分析一下這個提升過程,這個程式碼會以近似如下形式進行處理:
function getName() {
console.log(5);
}
var getName //被忽略
getName = function () {
console.log('4');
};
getName();
getName
函式和變數getName
被提升- 然後
var getName
因為重複宣告被忽略 - 最後函式表示式
getName
會覆蓋函式getName
所以最後實際執行的是函式表示式getName = function () { console.log('4'); };
,結果為’4’
5.2 例子2
function showName() {
console.log('name1');
}
showName();
function showName() {
console.log('name2');
}
showName();
想想這個結果是什麼?
有了上面經驗,這個就更好理解了,這個程式碼會以近似如下形式進行處理:
function showName() {
console.log('name1');
}
function showName() {
console.log('name2');
}
showName();
showName();
所以結果一目瞭然,結果為 name2 name2
6.總結
通過上面的內容,我們可以進行一個簡單的總結:
- 變數提升,是指在 JavaScript 程式碼執行過程中,JavaScript 引擎把變數的宣告部分和函式的宣告部分提升到程式碼開頭的“行為”。變數被提升後,會給變數設定預設值,這個預設值就是我們熟悉的 undefined。
- JS的程式碼執行順序是先進行宣告處理,然後進行賦值等其他操作。
- 提升的過程就像是把變數和函式宣告從他們在程式碼中出現的位置“移動”到了最上面。另外只有定義的宣告本身會被提升,而賦值或其他執行邏輯會留在原地
- 函式宣告和變數宣告在一起時,函式首先被提升,然後才是變數。