PHP 與 Javascript 是我日常使用的程式語言, 但是其變數的 scope (範疇, 作用域, 有效範圍) 有些不同, 偶而恍神的時候會兩者互相混淆, 故在此紀錄一下兩者差別的測試結果.
首先來看 PHP 變數的 scope. PHP 的變數分為函式外宣告的全域變數與函式內宣告的區域變數兩種, 即使同名也不會互相干擾, 全域變數作用域為整個網頁檔案, 即使在不同的 <?php ?>區段中都是指涉同一個變數, 例如下列範例 1 所示 :
測試範例 1 : http://mybidrobot.allalla.com/phptest/scope_1.php [看原始碼]
$a=1; //全域變數
function func(){
$a=2; //區域變數不會改變全域變數之值
echo $a;
}
func(); //顯示 2 (顯示區域變數之值)
echo $a; //顯示 1 (顯示全域變數之值, 不會被函式修改)
結果顯示 21, 可見函式中的程式碼看不到外面的全域變數, 因此無法存取外面的 $a 之值. 相同的, 函式外面的程式碼也看不到函式內的區域變數. 如果想要在函式內存取全域變數, 必須在函式內用 global 宣告其為全域變數, 如下列範例 2 所示 :
測試範例 2 : http://mybidrobot.allalla.com/phptest/scope_2.php [看原始碼]
$a=1; //全域變數
function func(){
global $a; //宣告 $a 為全域變數
$a=2; //修改全域變數值為 2
echo $a;
}
func(); //顯示 2 (顯示全域變數之值)
echo $a; //顯示 2 (顯示全域變數之值)
可見在函式內部, 只要先用 global 宣告, 就可以存取外面的全域變數了, 上例中函式內的 $a 已指向外面的同名全域變數, 因此將其值改為 2, 事實上就是改了外面那個全域變數 $a 之值. 注意, global 必須獨立宣告, 不可以宣告同時賦值, 例如下列用法是錯誤的 :
global $a=2; //錯誤用法
如果我們傳參數進去會怎樣? 當然會因為 global 的關係改到外部全域變數之值啦, 如下列範例 2-1 所示 :
測試範例 2-1 : http://mybidrobot.allalla.com/phptest/scope_2_1.php [看原始碼]
$a=1; //全域變數
function func($a){
global $a; //宣告 $a 為全域變數
$a=2; //修改全域變數值為 2
echo $a; //顯示 2
}
func(3); //顯示 2 (顯示全域變數之值)
echo $a; //顯示 2 (顯示全域變數之值)
結果顯示 22, 雖然傳入 3, 但在函式內又被改為 2, 故輸出 2, 同時因為 global 緣故, 也把外部之同名全域變數改為 2.
除了 global 之外, 還可以利用超全域變數 $GLOBALS[] 陣列來存取全域變數 (關聯式陣列, 用變數名稱當索引), 如下列範例 3 所示 :
測試範例 3 : http://mybidrobot.allalla.com/phptest/scope_3.php [看原始碼]
$a=1; //全域變數
function func(){
$GLOBALS["a"]=2; //修改全域變數值為 2
echo $GLOBALS["a"];
}
func(); //顯示 2 (顯示全域變數之值)
echo $a; //顯示 2 (顯示全域變數之值)
可見效果是一樣的. 因為每宣告一個全域變數, PHP 就會在 $GLOBALS 陣列中添加一個元素, 儲存指向該全域變數的參考.
當函式呼叫完畢, 函式內的區域變數就會從記憶體中釋放, 例如下列範例 4 所示 :
測試範例 4 : http://mybidrobot.allalla.com/phptest/scope_4.php [看原始碼]
$a=1; //全域變數
function increment(){
echo ++$a; //沒有設值預設為 null, 增量計算時會轉為 0
}
echo $a; //顯示 1 (顯示全域變數之值)
increment(); //顯示 1 (null 值為 0 增量後為 1)
increment(); //顯示 1 (null 值為 0 增量後為 1)
結果顯示 111, 後面兩個 1 是呼叫 increment() 函式的結果, 因為區域變數 $a 沒有設值 (=null), 增量計算時會被 PHP 認為是整數, 而整數變數預設為 0, 故先轉為 0 再增量, 故輸出 1, 但是因為函式執行完畢就釋放記憶體中的區域變數, 因此沒辦法記憶其值, 不論呼叫幾次都是輸出 1. 若要記憶區域變數之值, 必須宣告為靜態變數, 如下列範例 5 所示 :
測試範例 5 : http://mybidrobot.allalla.com/phptest/scope_5.php [看原始碼]
$a=1; //全域變數
function increment(){
static $a;
echo ++$a; //增量
}
echo $a; //顯示 1 (顯示全域變數之值)
increment(); //顯示 1 (預設 null 值為 0 增量後為 1)
increment(); //顯示 2
對於 Javascript 來說, 它也是有分宣告於函式外的全域變數與函式內的區域變數, 如下列範例 6 所示 :
測試範例 6 : http://mybidrobot.allalla.com/phptest/scope_6.htm [看原始碼]
var a=1; //全域變數
function func(){
var a=2; //區域變數
document.write(a); //顯示 2
}
func(); //顯示 2
document.write(a); //顯示 1
結果顯示 21, 這結果與 PHP 是相同的, 亦即全域變數與區域變數互不侵犯, 函式內看不到外面的變數; 函式外也看不到函式內的變數. 但是如果我們將函式內的變數宣告關鍵字 var 拿掉, 結果就不同了, 如下列範例 7 所示 :
測試範例 7 : http://mybidrobot.allalla.com/phptest/scope_7.htm [看原始碼]
var a=1; //全域變數
function func(){
a=2; //函式內變數未宣告直接賦值, 解譯器將其視為全域變數
document.write(a); //顯示 2
}
func(); //顯示 2
document.write(a); //顯示 2
結果顯示 22, 很奇怪吧! 函式內的變數有沒有用 var 宣告是有很大差別的, 有用 var 宣告的就是區域變數, 即使與函式外的全域變數同名也沒關係, 井水不犯河水, 但若沒有用 var 宣告就直接使用, 就會被 Javascript 解譯器視同全域變數處理 (跟 PHP 使用 global 存取全域變數效果一樣), 如果與全域變數同名, 就會改變全域變數的值; 反過來說也是一樣, 函式內本該對外隱藏的區域變數就全都露了, 從函式封裝上來看, Javascript 有不夠嚴謹的弱點, 這是與 PHP 的不同之處. 寫 Javascript 要養成良好習慣, 最好用 var 宣告變數.
Javascript 的變數可以不宣告直接賦值, 其實是解譯器在執行前的預編譯階段自動幫我們加上去了, 但只對函式外的變數自動加 var, 不會對函式內的變數加 var, 所以範例 7 函式裡的 a 變數就被當成全域變數處理了. 但是, 如果有傳入參數, 那結果又不同了, 如下列範例 7-1 所示 :
測試範例 7-1 : http://mybidrobot.allalla.com/phptest/scope_7_1.htm [看原始碼]
var a=1; //全域變數
function func(a){ //傳入參數 a
a=2; //此 a 變數被視為參數 (區域變數), 故不會更改外部 a 之值
document.write(a); //顯示 2
}
func(3); //顯示 2
document.write(a); //顯示 1
結果顯示 21, 此例中函式 func() 帶了一個參數 a, 雖然函式內第一行 a=2 沒有用 var 宣告, 但是因為有傳入參數 a=3, 因此變數 a 會被解譯器視為傳入之參數, 而參數就是區域變數, 雖然傳入值為 3, 但隨即被改成 2, 故呼叫 func(3) 卻輸出 2. 當然它也不會改到全域變數 a 之值, 因此最後一行指令仍輸出 1. 這跟上面 PHP 的範例 2-1 結果截然不同.
最後來研究一下靜態變數, 在 Javascript 規格裡是沒有靜態變數的, 但範例 7 函式內未宣告的變數等同於全域變數, 這個特性倒是可以拿來當作靜態變數使用, 如下列範例 8 所示 :
測試範例 8 : http://mybidrobot.allalla.com/phptest/scope_8.htm [看原始碼]
var a=1; //全域變數
function increment(){
++a; //函式內變數未宣告直接賦值, 解譯器將其視為全域變數
}
increment(); //a 增量為 2
increment(); //a 增量為 3
document.write(a); //顯示 3
可見因為函式內同名變數未宣告即賦值, 被視為全域變數, 當函式執行完畢時, a 不會消失, 所以每呼叫一次就增量, 跟上面 PHP 的 static 變數效果一樣.
1 則留言 :
謝謝解惑
張貼留言