使用 var 宣告變數

July 19, 2022

在程式語言的分類中,依據是在編譯時期或執行時期進行型別檢查,可區分為靜態定型(Statically-typed)語言與動態(Dynamically-typed)語言。

語言的定型

Java、C/C++ 等皆為靜態定型語言,其變數必然帶有型態。以 Java 為例:

int number = 10;
String id = "caterpillar";

在上例中,number 變數本身帶有 int 型態資訊,而 id 變數帶有 String 型態資訊,在指定時,變數型態資訊與值的型態資訊必須符合,否則會發生編譯失敗。例如以下就會因型態不符而編譯失敗:

int number = 10;
number = "caterpillar";

JavaScript 為動態定型語言,其變數本身使用者無需宣告型態,型態資訊僅在值或物件本身,變數只用來作為取得值或物件的參考。例如:

var some = 10;
some = 'caterpillar';

由於變數本身不帶型態資訊,同一個變數可以指定不同型態的值,實際操作時,是在執行時期才透過變數來參考至物件或值,才得知物件或值上有操作之方法。

靜態定型語言由於變數本身帶有型態資訊,好處就是編譯時期,可由編譯器確認變數與實際參考之值是否符合,可在編譯時期就檢查出許多型態指定不符的錯誤。相對地,動態定型語言就必須等到執行時期,才能發現所操作的對象並非預期型態之錯誤,這是靜態定型語言優點動態定型語言的地方。

然而,靜態定型語言宣告變數時,必須同時宣告型態,指定值給變數時亦需符合型態,或者是使用轉型(CAST)語法,讓編譯器忽略型態不符問題,因而容易造成語法上的冗長。例如在 Java 中,若要使用同一陣列儲存多種物件,則一個例子如下:

Object[] objects = {"caterpillar", new Integer(100), new Date()};
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 宣告

再回頭看看 JavaScript 變數宣告的討論。在 JavaScript 中要宣告變數,可以使用 var 來宣告。在函式中宣告的話,作用範圍是在函式之中,例如:

function func() {
	var y = 20;
}

func();

console.log(y);

執行時這個 .js 檔案中的程式碼時,y 使用了 var 宣告,所以在函式外不可見,為函式中的區域變數。

實際上,在 JavaScript 中,變數會是某個物件的特性,例如,在全域使用 var 宣告變數的話,會在全域物件上建立特性。

全域變數若在瀏覽器中,就是 window 物件,在 Node.js 的互動環境中,也可以在全域範圍中使用 this 來取得(如果是使用 node 指令直譯 .js 檔案,則要使用 global 名稱,之後文件還會談到 this 是什麼)。例如:

> var x = 10;
undefined
> this.x;
10
>

如果全域與區域中有同名的變數,則區域會暫時覆蓋全域:

var x = 10;

function func() {
	var x = 20;
	console.log(x); // 顯示 20
}

func();

console.log(x);  // 顯示 10

物件特性與 var

你可以使用 delete 來刪除物件上的特性。例如:

> var o = {};
undefined
> o.x = 10;
10
> o.x;
10
> delete o.x;
true
> o.x
undefined
>

如果直接指定值給全域物件成為特性,也可以使用 delete 刪除,例如:

> this.x = 10;
10
> x;
10
> delete this.x;
true
> x;
ReferenceError: x is not defined
	at repl:1:1
	at ContextifyScript.Script.runInThisContext (vm.js:50:33)
	at REPLServer.defaultEval (repl.js:240:29)
	at bound (domain.js:301:14)
	at REPLServer.runBound [as eval] (domain.js:314:12)
	at REPLServer.onLine (repl.js:441:10)
	at emitOne (events.js:121:20)
	at REPLServer.emit (events.js:211:7)
	at REPLServer.Interface._onLine (readline.js:282:10)
	at REPLServer.Interface._line (readline.js:631:8)
>

delete 會傳回 true 表示特性刪除成功,如果想刪除的特性並非可組態的(Configurable)(之後文件會看到如何定義物件特性是否為可組態),嚴格模式下會發生 TypeError。例如,陣列的 length 就不是可組態的的特性:

> var arr = [];
undefined
> arr.length;
0
> delete arr.length;
TypeError: Cannot delete property 'length' of [object Array]
	at repl:1:1
	at ContextifyScript.Script.runInThisContext (vm.js:50:33)
	at REPLServer.defaultEval (repl.js:240:29)
	at bound (domain.js:301:14)
	at REPLServer.runBound [as eval] (domain.js:314:12)
	at REPLServer.onLine (repl.js:441:10)
	at emitOne (events.js:121:20)
	at REPLServer.emit (events.js:211:7)
	at REPLServer.Interface._onLine (readline.js:282:10)
	at REPLServer.Interface._line (readline.js:631:8)
>

使用 var 宣告的變數,嚴格模式下不能使用 delete 刪除,就算是在全域宣告時,變數實際上是全域物件的特性也不行,這會引發 SyntaxError

> var x = 10;
undefined
> delete x;
delete x;
		^

SyntaxError: Delete of an unqualified identifier in strict mode.

> delete this.x;
TypeError: Cannot delete property 'x' of #<Object>
	at repl:1:1
	at ContextifyScript.Script.runInThisContext (vm.js:50:33)
	at REPLServer.defaultEval (repl.js:240:29)
	at bound (domain.js:301:14)
	at REPLServer.runBound [as eval] (domain.js:314:12)
	at REPLServer.onLine (repl.js:441:10)
	at emitOne (events.js:121:20)
	at REPLServer.emit (events.js:211:7)
	at REPLServer.Interface._onLine (readline.js:282:10)
	at REPLServer.Interface._line (readline.js:631:8)
>

嚴格模式中,對 var 宣告的變數使用 delete 會直接拋出 SyntaxError,而不是傳回 false

var 範疇

你可以重複使用 var 宣告變數,若宣告時沒有指定值,就不會覆蓋原有的指定值。例如:

> var x = 10;
undefined
> var x;
undefined
> x
10
> 

var 宣告的變數是當時作用範圍中整個都是有作用的,並沒有所謂區塊範圍。例如:

function func() {
	if(true) {
		var x = 10;
	}
	return x;
}

console.log(func()); 

執行結果會顯示 10。var 宣告的變數是當時作用範圍中整個都是有作用的,這會產生令人驚奇的結果。例如下例不意外的,會產生直譯錯誤:

function func() {
	console.log(x);
}

func();

但下例中並不會直譯錯誤:

function func() {
	console.log(m);
	var m = 10;
	console.log(m);
}

func();

結果會顯示 undefined 與 10。所有 var 宣告的變數,在整個函式區塊中都是可見的,因而在上例中 console.log 時是可找到 m 特性,只不過是 undefined 的值,這行為稱為提昇(Hoisting)。

那如果是這個呢?

var m = 20;

function func() {
	console.log(m);  // undefined
	var m = 10;
	console.log(m);  // 10
}

func();

由於 var 宣告的變數,在函式範圍中都會有作用,因此,在 func 中宣告的 m,提昇並暫時覆蓋 func 外的 m,然而,func 第一次遇到 m 時,還沒有進行指定值,因此會是 undefined

在嚴格模式下,有一些字被保留作未來使用,你不可以使用這些保留字作為變數名稱:

  • implements
  • interface
  • package
  • private
  • protected
  • public
  • static
  • yield

除此之外,因為 JavaScript 本身有個 eval 函式,而每個函式中 arguments 也用來參考至引數清單,在嚴格模式下 eval 與 arguments 也不能作為變數名稱。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter