avatar

目錄
JavaScript 作用域

作用域(Scope)

變數的地盤有多大、能生效的範圍有多廣,一切取決變數誕生的地方與宣告的方式。這個範圍就稱為這個變數的作用域(Scope)。

作用域的類型分為兩種:

動態作用域(dynamic scope):以呼叫函式的地方當作作用域鏈。
靜態作用域(static scope)、詞法作用域(lexical scope):以宣告函式的地方當作作用域鏈。

JavaScript 所採用的是靜態作用域。

在 Javascript 中,作用域分為三個等級:

Function Level Scope

函式內宣告的變數。

宣告位置:function 內。
有效範圍:該 function 之內。
說明:
在 Function 內,從宣告開始到最後都有效。
在 Function 外就變成未定義。

Code
1
2
3
4
5
6
7
function myFunc(){
var n1 = "OneJar";
console.log("myFunc(): n1=", n1);
}

myFunc(): n1= OneJar
Global: typeof n1= undefined

Global Level Scope

每個執行 JavaScript 程式的環境,會有一個全域物件 (Global Object)。全域物件裡的變數,無論在哪裡宣告,效力都能遍及整個程式,我們稱為全域變數 (Global Variables)。

HTML:全域物件是 window object。
Node.js:全域物件是 global object。

宣告位置:主程式內,任何 Function 之外。
有效範圍:整個程式,包含所有 Function 內。
說明:
無論 Function 內外都能使用。
呼叫方式可以是直接呼叫變數名稱,或透過全域物件 window 去呼叫。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function myFunc(){
console.log("myFunc(): n1=", n1);
console.log("myFunc(): this.n1=", this.n1);
console.log("myFunc(): window.n1=", window.n1);
}

var n1 = "OneJar";
myFunc();
console.log("Global: n1=", n1);

myFunc(): n1= OneJar
myFunc(): this.n1= OneJar
myFunc(): window.n1= OneJar
Global: n1= OneJar

ps.Local 內的變數有可能自動轉成 Global 變數。
在 JavaScript 裡有一種狀況會自動產生全域變數,那就是賦值給未宣告的變數。

Code
1
2
3
4
5
6
function myFunc(){
n1 = "OneJar"; // 自動變成一個 Global 變數
console.log("myFunc(): n1=", n1);
console.log("myFunc(): this.n1=", this.n1);
console.log("myFunc(): window.n1=", window.n1);
}

若是 Global 和 Function 內宣告了同樣名稱的變數,在 Function 內會是 Local 變數生效,Function 外依然是以 Global 為主。如果在 Function 內指定用 Global 變數(window.xx)的話,以 Global 為主。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myFunc(){
var n1 = "Stephen Chow";
console.log("myFunc(): n1=", n1);
console.log("myFunc(): this.n1=", this.n1);
console.log("myFunc(): window.n1=", window.n1);
}

var n1 = "Tom Cruise";
myFunc();
console.log("Global: n1=", n1);

myFunc(): n1= Stephen Chow
myFunc(): this.n1= Tom Cruise
myFunc(): window.n1= Tom Cruise
Global: n1= Tom Cruise

Block Level Scope (ES6 let、const)

Block 指的是一段大括號{}包起來的區塊。

宣告位置:Block 內。
有效範圍:該 Block 之內。
說明:
在 Block 內,從宣告開始到最後都有效。
在 Block 外就變成未定義。

Code
1
2
3
4
5
6
7
8
{
let x = 2;
{
console.log(x); // 2
}
console.log(x); // 2
}
console.log(x); // ReferenceError: x is not defined

範圍鏈、或稱作用鏈(Scope Chain)

當在該執行環境中的物件找不到變數時,就會透過作用鏈的機制來向宣告函式的外層尋找。

e.g.

Code
1
2
3
4
5
6
7
8
9
var a = 1;
function x() {
var a = 2;
y()
}
function y() {
console.log(a) // 1
}
x();

函式 y 是在全域執行環境下被初始化的,因此函式 y 的 scope chain 除了自己本身之外,另一個就是找到全域執行環境當中,因此輸出 1。

Code
1
2
3
4
5
6
7
8
9
10
var a = 1;
function x() {
var a = 2;
function y() {
console.log(a) // 2
}
y()
}

x();

函式 y 是在函式執行環境 x 下被初始化的,但本身沒有 a 變數,因此向外尋找,從 y() –> x() –> global environment 查找 a 變數的過程,就是 Scope chain。在此範例中,因在 x() 就找到 a = 2,所以不再往外尋找。

Code
1
2
3
4
5
6
7
8
9
10
11
var a = 1;
function x() {
a = 2; // local 轉成 global 變數
y()
}

function y() {
console.log(a) // 2
}

x();

函式 y 是在全域執行環境下被初始化的,因此函式 y 的 scope chain 除了自己本身之外,另一個就是找到全域執行環境當中的 x,執行 x() 時,內部的 a 去更動了全域環境的 a 值,因此輸出 2。

參考:
https://ithelp.ithome.com.tw/articles/10203387
https://shawnlin0201.github.io/JavaScript/JavaScript-Scope/#more
https://pjchender.blogspot.com/2015/12/javascriptscope-chainouter-environment.html