highlight.js

星期日, 3月 24, 2013

JavaScript 的 var

今天有同事問我 JavaScript 變數有加 var 宣告跟沒有有什麼差?我聽了也是一愣, 後來去查了文件才發現, 原來 var 不 var 可有差了, 主要大概可以歸納為這兩點:
  1. JavaScript 是以 function 來建立新的變數 scope, 只要是在 function 之外宣告的變數都是全域變數, 也就是全域物件的屬性, 而只有全域變數可以不用加 var 宣告, 就可以直接使用。
  2. 有加 var 宣告的全域變數是全域物件 (在瀏覽器中就是 window) 的 non-configurable 屬性, 比如說你就無法使用 delete 刪除以 var 宣告的全域變數。
舉例來說, 以這個 html 檔為例:
<!DOCTYPE html>
<html>
<head>
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
</head>
<body>
  <div id="da"></div>
  <div id="db"></div>
  <div id="dc"></div>
  <div id="dd"></div>
<script>
i = 10;
var j = 20; 
this.i = 100;
this.j = 200;

$("#da").text(i);
$("#db").text(j);
$("#dc").text(this.i);
$("#dd").text(window.j);
</script>
 
</body>
</html>
實際輸出的結果就是:
100
200
100
200
你可以看到不論是用 i 或是 this.i 都是同一份資料, 這是因為 this 指向全域物件, 所以也可以用 window.j 以存取 j, 因為在瀏覽器中, window 就是全域物件。

如果你在剛剛的 JavaScript 程式中插入兩航程式, 嘗試用 delete 刪除 i 與 j:
i = 10;
var j = 20; 
this.i = 100;
this.j = 200;

delete this.i;
delete this.j;

$("#da").text(i);
$("#db").text(j);
$("#dc").text(this.i);
$("#dd").text(window.j);
在瀏覽器的 JavaScript console 中就會看到以下的錯誤訊息:
Uncaught ReferenceError: i is not defined
這是因為 i 並沒有使用 var 宣告, 是全域物件的 configurable 屬性, 因此可以用 delete 刪除, 所以當執行到底下存取變數 i 的值時, i 變數就已經不存在了。

事情還沒完, 如果你是使用 node.js, 那麼又複雜了一點, 使用 var 宣告的全域變數實際上不是真的全域, 而只能在模組 (可簡單看成是檔案) 內存取, 出了模組, 就無法存取了。以底下的程式為例:
i = 10;
var j =20;

console.log(global.i);
console.log(global.j);
console.log(i);
console.log(j);
執行結果如下:
10
undefined
10
20
透過 node.js 的全域物件 global 可存取到沒有使用 var 宣告的 i 變數, 這是真正全域的變數。可是透過 global 物件嘗試存取 j 變數時, 就會看到 global.j 是未定義的屬性, 這表示 j 並不是真正全域的變數。

如果另外寫個程式把剛剛的測試程式當成模組載入:
var tv = require("./testvar.js");

console.log("global==============");
console.log(i);
//console.log(j);
輸出結果如下:
10
undefined
10
20
global==============
10
可以看到在程式中可存取到在另一個檔案中宣告的變數 i, 的確是全域變數無誤。若是把程式中最後一行的註解拿掉, 嘗試存取 j, 就會看到:
10

D:\mee\My Box Files\practice\jswebapp\testvar2.js:5
console.log(j);
^
ReferenceError: j is not defined
at Object. (D:\mee\My Box Files\practice\jswebapp\testvar2.js:5:13)
可以看到 j 並未定義, 這是因為 j 是用 var 在其他檔案中宣告的變數, 不是全域變數, 所以無法存取。