前端學習(34)~js學習(十一):作用域和變量提升
- 2020 年 3 月 18 日
- 筆記
作用域:通俗來講,作用域是一個變量或函數的作用範圍。作用域在函數定義時,就已經確定了。
目的:為了提高程序的可靠性,同時減少命名衝突。
作用域的分類
- 全局作用域:作用於整個 script 標籤內部,或者作用域一個獨立的 JS 文件。
- 函數作用域(局部作用域):作用於函數內的代碼環境。
作用域的訪問關係
在內部作用域中可以訪問到外部作用域的變量,在外部作用域中無法訪問到內部作用域的變量。
代碼舉例:
var a = 'aaa'; function foo() { var b = 'bbb'; console.log(a); // 打印結果:aaa。說明 內層作用域 可以訪問 外層作用域 里的變量 } foo(); console.log(b); // 報錯:Uncaught ReferenceError: b is not defined。說明 外層作用域 無法訪問 內層作用域 里的變量
變量的作用域
根據作用域的不同,變量可以分為兩類:全局變量、布局變量。
全局變量:
- 在全局作用域下聲明的變量,叫「全局變量」。在全局作用域的任何一地方,都可以訪問這個變量。
- 在全局作用域下,使用 var 聲明的變量是全局變量。
- 特殊情況:在函數內不使用 var 聲明的變量也是全局變量(不建議這麼用)。
局部變量:
- 定義在函數作用域的變量,叫「局部變量」。
- 在函數內部,使用 var 聲明的變量是局部變量。
- 函數的形參也是屬於局部變量。
從執行效率來看全局變量和局部變量:
- 全局變量:只有瀏覽器關閉時才會被銷毀,比較占內存。
- 局部變量:當其所在的代碼塊運行結束後,就會被銷毀,比較節約內存空間。
作用域的上下級關係
當在函數作用域操作一個變量時,它會先在自身作用域中尋找,如果有就直接使用(就近原則)。如果沒有則向上一級作用域中尋找,直到找到全局作用域;如果全局作用域中依然沒有找到,則會報錯 ReferenceError。
在函數中要訪問全局變量可以使用window對象。(比如說,全局作用域和函數作用域都定義了變量a,如果想訪問全局變量,可以使用window.a
)
全局作用域
直接編寫在script標籤中的JS代碼,都在全局作用域。
- 全局作用域在頁面打開時創建,在頁面關閉時銷毀。
- 在全局作用域中有一個全局對象window,它代表的是一個瀏覽器的窗口,由瀏覽器創建,我們可以直接使用。
在全局作用域中:
- 創建的變量都會作為window對象的屬性保存。比如在全局作用域內寫
var a = 100
,這裡的a
等價於window.a
。 - 創建的函數都會作為window對象的方法保存。
變量的聲明提前(變量提升)
使用var關鍵字聲明的變量( 比如 var a = 1
),會在所有的代碼執行之前被聲明(但是不會賦值),但是如果聲明變量時不是用var關鍵字(比如直接寫a = 1
),則變量不會被聲明提前。
舉例1:
console.log(a); var a = 123;
打印結果:undefined。注意,打印結果並沒有報錯,而是 undefined,說明變量 a 被提前聲明了,只是尚未被賦值。
舉例2:
console.log(a); a = 123; //此時a相當於window.a
程序會報錯:Uncaught ReferenceError: a is not defined
。
舉例3:
a = 123; //此時a相當於window.a console.log(a);
打印結果:123。
舉例4:
foo(); function foo() { if (false) { var i = 123; } console.log(i); }
打印結果:undefined。注意,打印結果並沒有報錯,而是 undefined。這個例子,再次說明了:變量 i 在函數執行前,就被提前聲明了,只是尚未被賦值。
函數的聲明提前
函數聲明:
使用函數聲明
的形式創建的函數function foo(){}
,會被聲明提前。
也就是說,整個函數會在所有的代碼執行之前就被創建完成。所以,在代碼順序里,我們可以先調用函數,再定義函數。
代碼舉例:
fn1(); // 雖然 函數 fn1 的定義是在後面,但是因為被提前聲明了, 所以此處可以調用函數 function fn1() { console.log('我是函數 fn1'); }
函數表達式:
使用函數表達式
創建的函數var foo = function(){}
,不會被聲明提前,所以不能在聲明前調用。
很好理解,因為此時foo被聲明了(這裡只是變量聲明),且為undefined,並沒有把 function(){}
賦值給 foo。
所以說,下面的例子,會報錯:

函數作用域
提醒1:在函數作用域中,也有聲明提前的特性:
- 函數中,使用var關鍵字聲明的變量,會在函數中所有的代碼執行之前被聲明。
- 函數中,沒有var聲明的變量都是全局變量,而且並不會提前聲明。
舉例:
var a = 1; function foo() { console.log(a); a = 2; // 此處的a相當於window.a } foo(); console.log(a); //打印結果是2
上方代碼中,執行foo()後,函數裏面的打印結果是1
。如果去掉第一行代碼,執行foo()後,函數裏面的打印結果是Uncaught ReferenceError: a is not defined
。
提醒2:定義形參就相當於在函數作用域中聲明了變量。
function fun6(e) { // 這個函數中,因為有了形參 e,此時就相當於在函數內部的第一行代碼里,寫了 var e; console.log(e); } fun6(); //打印結果為 undefined fun6(123);//打印結果為123
JavaScript 沒有塊級作用域(ES6之前)
在其他編程語言中(如 Java、C#等),存在塊級作用域,由{}
包括起來。比如在 Java 語言中,if 語句里創建的變量,只能在if語句內部使用:
if(true){ int num = 123; system.out.print(num); // 123 } system.out.print(num); // 報錯
但是,在 JS 中沒有塊級作用域(ES6之前)。舉例如下:
if(true){ var num = 123; console.log(123); //123 } console.log(123); //123(可以正常打印)
作用域鏈
引入:
- 只要是代碼,就至少有一個作用域
- 寫在函數內部的局部作用域
- 如果函數中還有函數,那麼在這個作用域中就又可以誕生一個作用域
基於上面幾條內容,我們可以得出作用域鏈的概念。
作用域鏈:內部函數訪問外部函數的變量,採用的是鏈式查找的方式來決定取哪個值,這種結構稱之為作用域鏈。查找時,採用的是就近原則。
代碼舉例:
var num = 10; function fn() { // 外部函數 var num = 20; function fun() { // 內部函數 console.log(num); } fun(); } fn();
打印結果:20。