在程式語言的分類中,依據是在編譯時期或執行時期進行型別檢查,可區分為靜態(Static-type)語言與動態(Dynamic-type)語言。
Java、C/C++等皆為靜態語言,其變數必然帶有型態。以Java為例:
int number = 10;
String id = "caterpillar";
String id = "caterpillar";
在上例中,number變數本身帶有int型態資訊,而id變數帶有String型態資訊,在指定時,變數型態資訊與值的型態資訊必須符合,否則會發生編譯失敗。例如以下就會因型態不符而編譯失敗:
int number = 10;
number = "caterpillar";
number = "caterpillar";
JavaScript則為動態語言,其變數本身使用者無需宣告型態,型態資訊僅在值或物件本身,變數只用來作為取得值或物件的參考。例如:
var some = 10;
some = 'caterpillar';
some = 'caterpillar';
由於變數本身不帶型態資訊,同一個變數可以指定不同型態的值,實際操作時,是在執行時期才透過變數來參考至物件或值,才得知物件或值上有操作之方法。
靜態語言由於變數本身帶有型態資訊,好處就是編譯時期,可由編譯器確認變數與實際參考之值是否符合,可在編譯時期就檢查出許多型態指定不符的錯誤。相對地,動態語言就必須等到執行時期,才能發現所操作的對象並非預期型態之錯誤,這是靜態語言優點動態語言的地方。
然而,靜態語言宣告變數時,必須同時宣告型態,因而容易造成語法上的冗長。例如在Java中,若要使用同一陣列儲存多種物件,則一個例子如下:
Object[] objects = {"caterpillar", new Integer(100), new Date()};
String name = (String) objects[0];
Integer score = (Integer) objects[1];
Data time = (Date) objects[2];
String name = (String) objects[0];
Integer score = (Integer) objects[1];
Data time = (Date) objects[2];
反觀JavaScript若要達到相同目的,所需的程式碼較為簡短。例如:
var objects = ['caterpillar', 100, new Date()];
var name = objects[0];
var score = objects[1];
var time = objects[2];
var name = objects[0];
var score = objects[1];
var time = objects[2];
就程式撰寫速度上,動態語言著實有著比靜態語言快速的優點。
再回頭看看JavaScript變數宣告的討論。在JavaScript中要宣告變數,可以使用var來宣告。這是先前一直都有看到的,事實上,你也可以不用var宣告,直接指定某個名稱的值,該名稱會自動成為全域範圍,其實也就是在全域(global)物件上建立特性。例如:
js> some = 10;
10
js> some;
10
js>
10
js> some;
10
js>
這很方便,也很危險,因為是在全域物件上建立特性。全域變數若在瀏覽器中,就是window物件,在 Rhino Shell 中,也可以在全域範圍中使用this來取得。例如:
js> this.some;
10
js> this;
[object global]
js>
10
js> this;
[object global]
js>
使用var所宣告的變數,作用範圍是在當時所在環境,不使用var直接指定值而建立的變數,則是全域物件上的一個特性,也就是俗稱的全域範圍。你可以先以這樣的觀念理解,如果你寫下:
some = 10;
執行時可先以直譯器會直接這麼作來理解:
this.some = 10;
例如,可觀察以下在函式中使用var與不使用var宣告的變數之差別:
js> function func() {
> var x = 10;
> y = 20;
> }
js> func();
js> x;
js: "<stdin>", line 12: uncaught JavaScript runtime exception: ReferenceError: "x" is not defined.
at <stdin>:12
js> y;
20
js> this.y;
20
js>
> var x = 10;
> y = 20;
> }
js> func();
js> x;
js: "<stdin>", line 12: uncaught JavaScript runtime exception: ReferenceError: "x" is not defined.
at <stdin>:12
js> y;
20
js> this.y;
20
js>
x在函式中使用var宣告,所以在函式外不可見,但y並非使用var宣告,所以y是全域物件上的特性,在函式外依舊可見,俗稱全域範圍。
如果你在全域使用var宣告變數,也相當於在全域物件上建立特性。例如:
js> var x = 10;
js> this.x;
10
js>
js> this.x;
10
js>
如果全域與區域中有同名的變數,則區域會暫時覆蓋全域:
js> var x = 10;
js> function func() {
> var x = 20;
> print(x);
> }
js> func();
20
js> print(x);
10
js>
js> function func() {
> var x = 20;
> print(x);
> }
js> func();
20
js> print(x);
10
js>
你可以使用delete來刪除物件上的特性。由於未使用var宣告的變數,會是全域物件上的特性,就某些意義來說,對未使用var宣告的變數使用delete,就相當於所謂刪除變數。例如:
js> a = 20;
20
js> delete a;
true
js> a
js: "<stdin>", line 7: uncaught JavaScript runtime exception: ReferenceError: "a" is not defined.
at <stdin>:7
js> this.a;
js>
20
js> delete a;
true
js> a
js: "<stdin>", line 7: uncaught JavaScript runtime exception: ReferenceError: "a" is not defined.
at <stdin>:7
js> this.a;
js>
注意,上例中,this.a的結果是undefined,不過在Rhino Shell中,undefined不會顯示任何結果。delete會傳回true表示特性刪除成功,false表示無法刪除。使用var宣告的變數就無法用delete刪除。例如:
js> var b = 10;
js> delete b;
false
js>
js> delete b;
false
js>
你可以重複使用var宣告變數,但不會覆蓋原有的指定值。例如:
js> var x = 10;
js> var x;
js> x
10
js> var x;
js> x
10
要注意的是,var宣告的變數是當時作用範圍中整個都是有作用的,並沒有所謂區塊範圍。例如:
js> function func() {
> if(true) {
> var x = 10;
> }
> return x;
> }
js> func();
10
js>
> if(true) {
> var x = 10;
> }
> return x;
> }
js> func();
10
js>
var宣告的變數是當時作用範圍中整個都是有作用的,這會產生令人驚奇的結果。例如下例不意外的,會產生直譯錯誤:
js> function func() {
> print(m);
> }
js> func();
js: "<stdin>", line 210: uncaught JavaScript runtime exception: ReferenceError:
"m" is not defined.
at <stdin>:210 (func)
at <stdin>:210
js>
> print(m);
> }
js> func();
js: "<stdin>", line 210: uncaught JavaScript runtime exception: ReferenceError:
"m" is not defined.
at <stdin>:210 (func)
at <stdin>:210
js>
但下例中並不會直譯錯誤:
js> function func() {
> print(m);
> var m = 10;
> print(m);
> }
js> func();
undefined
10
js>
> print(m);
> var m = 10;
> print(m);
> }
js> func();
undefined
10
js>
所 有var宣告的變數,在整個函式區塊中都是可見的,因而在上例中print()時是可找到m特性,只不過 是undefined的值。
如果你有興趣,範圍鏈(Scope chain) 會深入說明一件事,使用var所宣告的變數,會是當時執行環境(Execute context)中呼叫物件(call object)上的特性,也因此沒有所謂區塊範圍。