變數的聲明 在JavaScript程式中,使用一個變數之前應當先聲明,變數時使用關鍵字var來聲明的,如下所示:var num;var sum; 也可以寫成var num,sum,avg;如果只是聲明變數而沒有給變數賦值,預設的值是undefined 可以將變數的初始賦值和變數聲明合寫在一起如下所示 ...
變數的聲明
在JavaScript程式中,使用一個變數之前應當先聲明,變數時使用關鍵字var來聲明的,如下所示:
var num;
var sum;
也可以寫成
var num,sum,avg;
如果只是聲明變數而沒有給變數賦值,預設的值是undefined
可以將變數的初始賦值和變數聲明合寫在一起如下所示:
var h = "Hello",w = "World";
var a = 10, b = 20;
雖然使用關鍵字var進行變數的聲明,但JavaScript中變數類型屬於動態類型,所以不需要給變數指定數據類型,每當變數的值類型發生改變時,就會在內部將數據類型記錄下來。
所以:
var i = 10;
i = "Hello"
是合法的。
使用var語句重覆聲明變數是合法且有必要的,如果重覆聲明並且初始化了變數,那麼這就和一條簡單的賦值語句沒什麼兩樣,
如果你試圖讀取一個沒有聲明的的變數的值,JavaScript會報錯,在ESMAScript5嚴格模式中,給一個沒有聲明的變數賦值也會報錯,然而在非嚴格模式中,給一個沒有聲明的變數賦值,JavaScript會創建一個同名的全局變數,並讓它工作起來,但並不和全局變數完全一樣,這意味著你可以僥幸不聲明全局變數,但這不是一個號的編程習慣,並會造成很多BUG,因此應當使用var來聲明變數。
變數的作用域
一個變數的作用域是程式源碼中定義這個變數的區域,全局變數擁有全局作用域,在JavaScript代碼中的任何地方都是有意義的。
在函數內聲明的變數時局部變數,函數形參也是局部變數,它們只在函數體內有意義。
雖然在全局作用域可以不使用var聲明變數,但是在函數內必須使用var聲明變數。如果在函數內不使用var聲明變數,JavaScript會到全局變數中去尋找變數,如果在全局變數中找到則使用全局變數,如果在全局變數中也沒有找到,會創建一個全局變數。所以在函數體內聲明變數時要必須要使用var來進行聲明
var a = 10; function fun() { a = 20; b = 30; } fun() console.log(a,b)
函數作用域和聲明提前:
在類似C語言中,花括弧內的每一段代碼都具有各自的作用域,而且變數的聲明它們的代碼段之外是不可見的,我們稱之為塊級作用域,JavaScript中沒有塊級作用域,JavaScript取而代之的是使用函數作用域,JavaScript的函數作用域是指在函數體內聲明的所有變數在函數體內始終是可見的,JavaScript的這個特性被稱為聲明提前,即JavaScript函數里聲明的所有變數(不涉及賦值)都被提前至函數體的頂部。
var scope = "global"; function f(){ console.log(scope); // 輸出"undefined"而不是"global" var scope = "local"; // 定義變數並賦值 console.log(scope); // 輸出local }
f()
從直觀上看可能會認為第一行會輸出global,因為在執行函數內執行第一條列印語句時我們會想當然的認為JavaScript會到全局作用域中去找變數scope,所以第一行會列印global,但實際上由於函數作用域的特性,局部變數在整個函數體始終是有定義的,儘管如此,只有指定到var scope = "local";時才被賦值,因此上述過程等價於將函數體內的變數聲明提前至函數體頂部,同時變數初始化保留在原來的位置:
var scope = "global"; function f(){ var scope; // 變數聲明被置頂 console.log(scope); // 列印undefined var scope = "local"; // 定義變數並賦值 console.log(scope); // 輸出local }
通過上面的示例我們可以知道,在函數體內聲明變數時,儘量統一聲明到函數的頂部,而不是將變數聲明在要使用變數的地方。
作為屬性的變數
當聲明一個全局變數時,實際上是定義了一個全局對象的一個屬性,當使用var聲明一個變數時,創建的整個屬性時不可配置的,也就是整個變數無法通過delete運算符刪除,但如果沒有使用var聲明的變數,JavaScript會自動創建一個全局變數,這種類型的全局變數可以使用delete運算符進行刪除。如下:
var a = 1; b = 2; this.c = 3; delete a; // 不會被刪除 delete b; // 會被刪除 delete c; // 會被刪除 console.log(a); // 列印1 console.log(b) // 報錯 console.log(c) // 報錯
作用域鏈
JavaScript是基於詞法作用域的語言:通過閱讀包含變數定義在內的數行源碼就能知道變數的作用域。 全局變數在程式中始終都是有定義的。 局部變址在聲明它的函數體內以及其所嵌套的函數內始終是有定義的。
如果將一個局部變數看做是自定義實現的對象的屬性的話, 那麼可以換個角度來解讀變最作用域。 每一段JavaScript代碼(全局代碼或函數)都有一個與之關聯的作用域鏈(s cope chain)。 這個作用域鏈是一個對象列表或者鏈表, 這組對象定義了這段代碼”作用域中” 的變數。 當JavaScript需要查找變數x的值的時候(這個過程稱做 “變數解析" (variable resolution)) , 它會從鏈中的第一個對象開始查找, 如果這個對象有一 個名為x的屬性, 則會直接使用這個屬性的值, 如果第一個對象中不存在名為x的屬性, JavaScript會繼續查找鏈上的下一個對象。 如果第二個對象依然沒有名為x的屬性, 則會繼續查找下一個對象, 以此類推。 如果作用域鏈上沒有任何一個對象含有屬性X, 那麼就認為這段代碼的作用域鏈上不存在X, 並最終拋出一個引用錯誤(ReferenceError)異常。
在JavaScript的最頂層代碼中(也就是不包含在任何函數定義內的代碼), 作用域鏈由一個全局對象組成。 在不包含嵌套的函數體內, 作用域鏈上有兩個對象,第一個是定義函數參數和局部變數的對象,第二個是全局對象。在一個嵌套的函數體內,作用域鏈上至少有三個對象。理解對象鏈的創建規則是非常重要的,當定義一個函數時,他實際上保存一個作用域鏈,當調用這個函數時,它創建一個新的對象來存儲它的局部變數。並將這個對象添加至保存的哪個作用域鏈上,同時創建一個新的更長的表示函數調用作用域的鏈,對於嵌套函數來講,十強變得更加有趣,每次調用外部函數時,內部函數又會重新定義一遍,因為每次調用外部函數的時候,作用域鏈都是不同的,內部函數在每次定義的時候都有微妙的差別,在每次調用外部函數時,內部函數的代碼都是相同的,而且關聯這段代碼的作用域鏈也不相同。