前端學習(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);//列印結果為123JavaScript 沒有塊級作用域(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。
