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代碼之前首先要對代碼進行編譯,在編譯階段中有一部分工作就是找到所有的聲明,並用合適的作用域將他們關聯起來。
所以總結來說,包括變量和函數在內的所有聲明都會首先被處理,然後纔是代碼被執行。
當我們看到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.總結
通過上面的內容,我們可以進行一個簡單的總結:
- JS的代碼執行順序是先進行聲明處理,然後進行賦值等其他操作。
- 提升的過程就像是把變量和函數聲明從他們在代碼中出現的位置“移動”到了最上面。另外只有定義的聲明本身會被提升,而賦值或其他運行邏輯會留在原地
- 函數聲明和變量聲明在一起時,函數首先被提升,然後纔是變量。