語法作用域 Lexical scope

JavaScript 是採用「語法作用域」

  • 「lexical」的意思是「詞的,辭彙的」(adjective)

範例

先定義一個 function 叫做「callName」

1
2
3
4
5
6
function callName() {
var Ming = '小明';
console.log(Ming);
}

callName();

執行函式callName(),就會在 Console 印出console.log(Ming)

但是,如果我把console.log(Ming)移出「callName 函式」外

1
2
3
4
5
6
7
function callName() {
var Ming = '小明';

}

callName();
console.log(Ming);

在 Console 就會出現錯誤:「Ming is not defined」

原因為:
‼️ JavaScript 的作用域是在「函式裡面」,因此,在「函式裡面」宣告的變數,在外面是讀不到的

JavaScript 的「作用域」是「一層一層向內的」

  • 最外層:會有一個「全域」的作用域
  • 內層:有一個一個的 function 包著,各自的作用域是獨立的
    • 以下面的例子,有「callMefn2」這兩個 function,兩個 function 的作用域是「各自獨立」的

如果在 function 的作用域內需要某個變數,但是在 function 的作用域內沒有這個變數時,就會「往外層查找」

  • 如果外層有這個變數,就會直接取來使用
  • 如果外層也找不到這個變數,就會出現錯誤:「Uncaught ReferenceError: xxx is not defined」

語法作用域

靜態作用域(又稱為“語法作用域”)

🎃 靜態作用域,又稱為「語法作用域」–> 變數的作用域在語法解析時,就已經確定作用域,且作用域不會再改變

  • JavaScript 是採用「語法作用域」,跟「JavaScript 是如何運行的」有很大關係

  • JavaScript 是屬於「直譯式語言」–> 會直接透過「直譯器」解析原始碼之後生成代碼,接著就可以直接運行代碼

也就是說,當我在宣告 function 時,變數的作用域就已經確定了

語法作用域的範例

在下面的程式碼中,JavaScript 的執行順序是這樣的:

  1. 在外層先宣告一個變數–> var value = 1;
  2. 執行「fn2 函式」–> fn2();
  • 在執行「fn2 函式」時,先重新宣告「value變數 = 2」–> var value = 2;
  • 接著,再執行fn1();,會印出console.log(value);

這時,value變數的值會是多少呢?

因為 JavaScript 是屬於「語法作用域」,因此,在我撰寫 function 時,作用域就已經確定了

答案是:在「靜態作用域」,console.log(value)的值會等於「1」

1
2
3
4
5
6
7
8
9
var value = 1;
function fn1(){
console.log(value);
}
function fn2(){
var value = 2;
fn1();
}
fn2();

📍 首先,在外層宣告的變數var value = 1;,它的作用域是「全域」:

  • 因此,在「fn1 函式、fn2 函式」內,都可以讀取到value的值 = 1

📍 雖然在「fn2 函式」內,將「變數value」的值重新宣告為 2,但是var value = 2;的作用域就只限制在「fn2 函式」內

📍 因此,當我在「fn1 函式」執行console.log(value);時,就會「往外層查找變數value」–> 外層的value = 1

動態作用域

🎃 「動態作用域」–> 變數的作用域在「函式調用」時才決定

動態作用域的範例

在下面這段程式碼中,JavaScript 執行的順序是這樣的:

  1. 在全域宣告變數 var value = 1;
  2. 執行「fn2 函式」時,重新定義value = 2,接著會執行「fn1 函式」
  3. 在執行「fn1 函式」時,會去查找這個value的值:

因為「動態作用域」是在「函式調用」時,才會決定作用域

因此,「fn1 函式」會往「fn2 函式(也就是調用 fn1 函式 的地方)」查找value的值–> 在「fn2 函式」內的value = 2

1
2
3
4
5
6
7
8
9
var value = 1;
function fn1(){
console.log(value);
}
function fn2(){
var value = 2;
fn1();
}
fn2();

因此,如果是動態作用域,最後value的值會是「2」