Javascript變量提升以及函數(shù)提升

引入

下面的代碼變片段會(huì)如何輸出?

// 代碼段1
function func1() {
    var a = 1;
    function a() {}
    console.log(a);
}
func1();
  
// 代碼段2
function func2() {
    var a;
    function a() {}
    console.log(a);
}
func2();

運(yùn)行后,控制臺(tái)第一個(gè)console.log(a)會(huì)輸出1,而第二個(gè)console.log(2)則會(huì)輸出函數(shù)a。結(jié)果如下:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
1
[Function: a]

[Done] exited with code=0 in 0.044 seconds

Javascript運(yùn)行時(shí),會(huì)經(jīng)過(guò)三個(gè)階段^[《JavaScript預(yù)編譯過(guò)程理解
節(jié)選]:

  1. 語(yǔ)法分析階段:語(yǔ)法分析很簡(jiǎn)單,就是引擎檢查你的代碼有沒(méi)有什么低級(jí)的語(yǔ)法錯(cuò)誤;
  2. 預(yù)編譯階段:預(yù)編譯簡(jiǎn)單理解就是在內(nèi)存中開(kāi)辟一些空間,存放一些變量與函數(shù) ;
  3. 解釋執(zhí)行階段:解釋執(zhí)行顧名思義便是執(zhí)行代碼了;

Javascript具體執(zhí)行過(guò)程:

  1. 頁(yè)面產(chǎn)生便創(chuàng)建了GO全局對(duì)象(Global Object)(也就是window對(duì)象);
  2. 第一個(gè)腳本文件加載;
  3. 腳本加載完畢后,分析語(yǔ)法是否合法;
  4. 開(kāi)始預(yù)編譯
    • 查找變量聲明,作為GO屬性,值賦予undefined;
    • 查找函數(shù)聲明,作為GO屬性,值賦予函數(shù)體;

【注意】

  • 預(yù)編譯階段發(fā)生變量聲明和函數(shù)聲明,沒(méi)有初始化行為(賦值),匿名函數(shù)不參與預(yù)編譯 ;
  • 只有在解釋執(zhí)行階段才會(huì)進(jìn)行變量初始化 ;
  1. 預(yù)編譯兩個(gè)小規(guī)則
    1. 函數(shù)聲明整體提升—(具體點(diǎn)說(shuō),無(wú)論函數(shù)調(diào)用和聲明的位置是前是后,系統(tǒng)總會(huì)把函數(shù)聲明移到調(diào)用前面)
    2. 變量 聲明提升—(具體點(diǎn)說(shuō),無(wú)論變量調(diào)用和聲明的位置是前是后,系統(tǒng)總會(huì)把聲明移到調(diào)用前,注意僅僅只是聲明,所以值是undefined)
  2. 預(yù)編譯前奏
    1. imply global 即任何變量,如果未經(jīng)聲明就賦值,則此變量就位全局變量所有。(全局域就是Window)
    2. 一切聲明的全局變量,全是window的屬性;var a=12;等同于Window.a = 12;
    3. 函數(shù)預(yù)編譯發(fā)生在函數(shù)執(zhí)行前一刻;

變量提升

本小節(jié)將注重體會(huì)Javascript預(yù)編譯階段,變量如何創(chuàng)建以及賦值的。如:

// 定義兩個(gè)變量
var num1 = 50;
var num2 = 100;

在Javascript眼中代碼是這樣運(yùn)行的。

// 第一步:預(yù)編譯階段,將變量申明(定義)放在作用域最前面。
var num1;
var num2;
// 第二步:執(zhí)行賦值操作
num1 = 50;
num2 = 100;

再通過(guò)一個(gè)例子理解下:

function func(){
    var m = 10;
    console.log(m);
    console.log(n);
    var n = 20;
}
func();

其實(shí)在Javascript眼中他是這樣運(yùn)行的:

function func(){
    var m;
    var n;
    m = 10;
    console.log(m);
    console.log(n);
    n = 20;
}

此時(shí)控制臺(tái)輸出:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
10
undefined

[Done] exited with code=0 in 0.049 seconds

下面通過(guò)兩個(gè)函數(shù)同時(shí)運(yùn)行,比較下控制臺(tái)輸出結(jié)果:

function func(){
    var m = 10;
    console.log(m);
    console.log(n);
    var n = 20;
}
func();

console.log();

function funcT(){
    var m;
    var n;
    m = 10;
    console.log(m);
    console.log(n);
    n = 20;
}

funcT();

此時(shí)控制臺(tái)輸出結(jié)果如下所示:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
10
undefined

10
undefined

[Done] exited with code=0 in 0.045 seconds

兩個(gè)函數(shù)的執(zhí)行結(jié)果完全相同。所以 Javascript 并不是在定義一個(gè)變量的時(shí)候,聲明完成之后立即賦值,而是把所有用到的變量全部聲明之后,再到變量的定義的地方進(jìn)行賦值,變量的聲明的過(guò)程就是變量的提升。

變量在聲明提升的時(shí)候,是全部提升到作用域的最前面,一個(gè)接著一個(gè)的。但是在變量賦值的時(shí)候就不是一個(gè)接著一個(gè)賦值了,而是賦值的位置在變量原本定義的位置。原本js定義變量的地方,在js運(yùn)行到這里的時(shí)候,才會(huì)進(jìn)行賦值操作,而沒(méi)有運(yùn)行到的變量,不會(huì)進(jìn)行賦值操作。

所以變量的提升,提升的其實(shí)是變量的聲明,而不是變量的賦值。

函數(shù)提升

首先還是可以通過(guò)一個(gè)例子體會(huì)下函數(shù)提升的過(guò)程是怎樣的。

function func(){
    console.log("func運(yùn)行了");
}

// 申明前調(diào)用函數(shù)funcT();
funcT();

// 申明后調(diào)用函數(shù)func();
func();

function funcT(){
    console.log("funcT運(yùn)行了");
}

此時(shí)控制臺(tái)輸出:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
funcT運(yùn)行了
func運(yùn)行了

[Done] exited with code=0 in 0.046 seconds

此時(shí)兩個(gè)函數(shù)都能成功執(zhí)行。與變量提升不同的是,函數(shù)的提升是一步完成的。不同于變量提升是分成兩步運(yùn)行的,第一步是變量聲明的提升,第二步是變量的賦值。函數(shù)的提升是直接將整個(gè)函數(shù)整體提升到作用域的最開(kāi)始位置。

變量提升與函數(shù)提升的順序

依舊通過(guò)例子體會(huì):

console.log(a);
var a = 1;
console.log(a);
function a(){
    
}
console.log(a);

按照代碼的執(zhí)行順序,從上而下,理論上控制臺(tái)應(yīng)該是先輸出undefined、1a(){}。但是實(shí)際控制臺(tái)輸出如下:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
# 先輸出的是函數(shù)
[Function: a]
# 再輸出1;
1
# 最后還是輸出1
1

[Done] exited with code=0 in 0.051 seconds

為什么會(huì)這樣輸出呢?其實(shí)在JS眼中,代碼是這樣的

// 先發(fā)生變量提升,此時(shí)a如果能輸出的話(huà),肯定是輸出undefined。
var a;
// 函數(shù)提升覆蓋了定義的變量a的undefined,此時(shí)a;
function a(){
    
}
// 提升結(jié)束后,此時(shí)遇到了第一個(gè)console.log()。故此時(shí)輸出函數(shù)a
console.log(a);
// 帶代碼運(yùn)行到var a=1時(shí),a=1覆蓋了函數(shù)提升階段a的函數(shù)申明。
a = 1;
// 當(dāng)代碼運(yùn)行到第二個(gè)console.log()的時(shí)候,故輸出1;
console.log(a);
// 因?yàn)楹瘮?shù)申明提前了,所以無(wú)法再去覆蓋a的值。
// 所以第三個(gè)console.log()輸出的仍然是1。
console.log(a);

函數(shù)申明與函數(shù)表達(dá)式的順序

上面講解了變量提升,以及函數(shù)提升,下面再來(lái)理解前節(jié)中所提:函數(shù)表達(dá)式<span style="color:red;font-weight:bolder">必須先定義方可調(diào)用!</span>函數(shù)申明<span style="color:red;font-weight:bolder">則既可先定義再調(diào)用,也可先調(diào)用再定義。</span>這句話(huà)的意思。

仍然先看例子,下面的控制臺(tái)如何輸出?

console.log(testFunc);

console.log(func);

testFunc();

func();

function testFunc(){
    console.log("testFunc()執(zhí)行了");
}

var func = function (){
    console.log("func()執(zhí)行了");
}

控制臺(tái)輸出:

[Running] node "/Users/ermao/Development/javascript_code/homework/demo.js"
[Function: testFunc]
undefined
testFunc()執(zhí)行了
/Users/ermao/Development/javascript_code/homework/demo.js:7
func();
^

# 這個(gè)地方就報(bào)錯(cuò)了,無(wú)法執(zhí)行
TypeError: func is not a function
    at Object.<anonymous> (/Users/ermao/Development/javascript_code/homework/demo.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
    at internal/main/run_main_module.js:17:11

[Done] exited with code=1 in 0.047 seconds

從控制臺(tái)輸出可以看出,Javascript眼中代碼是這樣運(yùn)行的:

// 整個(gè)提升過(guò)程
var func; // 先提升了變量
// 再提升函數(shù)
function testFunc(){
    console.log("testFunc()執(zhí)行了");
}

// 代碼執(zhí)行階段
console.log(testFunc);  // 此時(shí)輸出函數(shù)體
console.log(func);      // 此時(shí)代碼并沒(méi)有運(yùn)行到賦值階段,所以還能輸出undefined

// 運(yùn)行函數(shù)
testFunc();             // 函數(shù)可以正常運(yùn)行
func();                 // 無(wú)法運(yùn)行,因?yàn)樵谔嵘A段是指當(dāng)做變量提升了,并沒(méi)有賦值。
                        // 所以func不能當(dāng)做函數(shù)來(lái)運(yùn)行
                        
// 當(dāng)執(zhí)行到func 賦值時(shí),為時(shí)已晚。報(bào)錯(cuò)后,無(wú)法繼續(xù)運(yùn)行了
func = function(){
    console.log("func()執(zhí)行了");
}

<span style="text-indent:2em;font-weight:bolder;color:red">就是說(shuō),函數(shù)提升僅適用于函數(shù)聲明,而不適用于函數(shù)表達(dá)式。</span>簡(jiǎn)而言之,函數(shù)表達(dá)式在預(yù)編譯提升階段是當(dāng)做變量進(jìn)行提升的,并沒(méi)有對(duì)變量進(jìn)行賦值。

那么箭頭函數(shù)能成功運(yùn)行嗎?請(qǐng)參考下面的代碼:

console.log(testFunc);

console.log(func);

testFunc();

func();

function testFunc(){
    console.log("testFunc()執(zhí)行了");
}

var func = () => {
    console.log("func()執(zhí)行了");
}

控制臺(tái)輸出如下所示,原因呢?

[Running] node "/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js"
[Function: testFunc]
undefined
testFunc()執(zhí)行了
/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js:7
func();
^

TypeError: func is not a function
    at Object.<anonymous> (/Users/ermao/Development/javascript_code/homework/tempCodeRunnerFile.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:945:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:962:10)
    at Module.load (internal/modules/cjs/loader.js:798:32)
    at Function.Module._load (internal/modules/cjs/loader.js:711:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1014:10)
    at internal/main/run_main_module.js:17:11

[Done] exited with code=1 in 0.045 seconds
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容