Javascript中函數提升和變量提升
詞法分析
詞法分析方法:
js運行前有一個類似編譯的過程即詞法分析,詞法分析主要有三個步驟:
- 分析參數
- 再分析變量的聲明
- 分析函數說明
具體步驟如下:
- 函數在運行的瞬間,生成一個活動對象(Active Object),簡稱AO
- 分析參數
- 函數接收形式參數,添加到AO的屬性,並且這個時候值為undefine,例如AO.age=undefine
- 接收實參,添加到AO的屬性,覆蓋之前的undefine
- 分析變量聲明,如var age;或var age=23;
- 如果上一步分析參數中AO還沒有age屬性,則添加AO屬性為undefine,即AO.age=undefine
- 如果AO上面已經有age屬性了,則不作任何修改
- 分析函數的聲明,如果有function age(){}
把函數賦給AO.age ,覆蓋上一步分析的值
代碼例子1
這樣我們先通過一段代碼來理解詞法分析:
詞法分析階段:
等價於:
- 首先形成Active Object即AO對象
- 第一步:分析形式參數
AO.age = undefine
傳入實參即對AO.age=undefine進行覆蓋:
AO.age = 3
- 第二步:分析局部變量
存在var age = 27;
這個時候遵循如果AO.age存在值則不作任何修改,按照第一步分析的最後結果AO.age = 3,所以這裏不作任何修改即:
AO.age = 3
- 第三步:分析函數的聲明,
因為函數中存在function age(){}函數
所以按照規則應該將函數賦值給AO.age覆蓋第二步分析的AO.age = 3即:
AO.age = function age(){}
執行階段
執行t1函數,到console.log(age)時,詞法分析的最後AO.age= function age(){},所以會打印:
function age(){}
var age=27;給age賦值27
到第二個console.log(age)這個時候age已經重新被賦值27,所以這個時候會打印:
27
function age() 並沒有調用所以並不會執行
到第三個console.log(age)這個時候age的值並沒有被再次修改,所以這個時候會打印:
27
運行js查看結果如下與我們分析的完全相符:
代碼例子2
和上面的詞法分析過程一樣
詞法分析階段:
等價於:
- 首先形成Active Object即AO對象
- 第一步:分析形式參數
AO.age = undefine
傳入實參即對AO.age=undefine進行覆蓋:
AO.age = 22
- 第二步:分析局部變量
第一步中最後得到AO.age = 22
所以這裏var age;以及var age =23 ,因為AO.age屬性已經存在值,所以這個時候遵循如果存在則不作任何修改,即:
AO.age = 22
- 第三步:分析函數的聲明,
因為函數中存在function age(){}函數
所以按照規則應該將函數賦值給AO.age覆蓋第二步分析的AO.age = 22即:
AO.age = function age(){}
執行階段
執行t1函數,到console.log(age)時,詞法分析的最後AO.age= function age(){},所以會打印:
function age(){}
var age=23;給age賦值23
到第二個console.log(age)這個時候age已經重新被賦值23,所以這個時候會打印:
23
function age() 並沒有調用所以並不會執行
到第三個console.log(age)這個時候age的值並沒有被再次修改,所以這個時候會打印:
23
運行js查看結果如下與我們分析的完全相符:
代碼例子3
詞法分析階段:
等價於:
- 首先形成Active Object即AO對象
- 第一步:分析形式參數
AO.age = undefine
傳入實參即對AO.age=undefine進行覆蓋:
AO.age = 22
- 第二步:分析局部變量
第一步中最後得到AO.age = 22,所以這裏遵循,如果AO.age存在值則不作任何修改即:
AO.age = 22
- 第三步:分析函數的聲明
因為函數中存在function age(){console.log(age)}函數
所以按照規則應該將函數賦值給AO.age覆蓋第二步分析的AO.age = 22即:
AO.age = function age(){console.log(age)}
執行階段
執行t1函數,到console.log(age)時,詞法分析的最後AO.age= function age(){console.log(age)},所以會打印:
function age(){console.log(age)}
age = 23,這個時候會覆蓋原來的function age(){console.log(age)},所以第二個console.log(age)會打印:
23
function age() 是一個函數表達式,所以不會做任何操作
age() 這個時候的age還是23,並不是函數表達式,所以這裏會報錯
運行js查看結果如下與我們分析的完全相符:
這裏的提示錯誤確實也是說age不是一個函數
代碼例子4
詞法分析階段:
- 首先形成Active Object即AO對象
- 第一步:分析形式參數
AO.age = undefine
傳入實參即對AO.age=undefine進行覆蓋:
AO.age = 23
- 第二步:分析局部變量
第一步中最後得到AO.age = 23,所以這裏遵循,如果AO.age存在值則不作任何修改即:
AO.age = 23
- 第三步:分析函數的聲明
因為函數中存在function age(){console.log(age)}函數
所以按照規則應該將函數賦值給AO.age覆蓋第二步分析的AO.age = 23即:
AO.age = function age(){console.log(age)}
執行階段
執行t1函數,到console.log(age)時,詞法分析的最後AO.age= function age(){console.log(age)},所以會打印:
function age(){console.log(age)}
function age() 是一個函數表達式,所以不會做任何操作
age()這個時候age是一個函數表達式,這裏會執行function age(){console.log(age)},這個時候函數裏console.log(age),age沒有被修改所以還是function age(){console.log(age)},即打印:
function age(){console.log(age)}
最後一個console.log(age)這裏的age沒有被修改還是function age(){console.log(age)},所以會打印:
function age(){console.log(age)}
運行js查看結果如下與我們分析的完全相符:
代碼例子5:
詞法分析階段:
- 首先形成Active Object即AO對象
- 第一步:分析形式參數
AO.age = undefine
傳入實參即對AO.age=undefine進行覆蓋:
AO.age = 23
- 第二步:分析局部變量
第一步中最後得到AO.age = 23,所以這裏遵循,如果AO.age存在值則不作任何修改即:
AO.age = 23
- 第三步:分析函數的聲明
這裏並沒有函數聲明表達式
所以最後分析的結果是:
AO.age = 23
執行階段
執行t1函數,到console.log(age)時,詞法分析的最後AO.age=23
所以第一個console.log(age)會打印
23
var age = function () {console.log(age)},這裏將var = 23進行覆蓋這個時候age是一個函數表達式
age() 正好調用function () {console.log(age)},這個時候這個函數裏的console.log(age),age並沒有修改還是一個函數表達式,所以會打印
function () {console.log(age)}
最後一個console.log(age)還是打印:
function () {console.log(age)}
運行js查看結果如下與我們分析的完全相符:
代碼例子6:
代碼例子6和代碼例子5的分析基本一樣,結果也是一樣:
總結:
函數提升比變量提升優先級高!
Javascript中函數提升和變量提升