通過對比js、php、c三門語言的for迴圈,加強自己對js變數作用域和php for迴圈的理解。 ...
為什麼要寫這篇文章呢?主要是給自己提個醒,js的水很深,需要小心點兒才能趟過去,更何況自己不是專業人士,那就得更加小心了。
看下麵的js代碼:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>javascript中變數的作用域</title> 6 </head> 7 <body> 8 9 <script> 10 for (var i = 0; i < 3; i++) { 11 console.log(i); 12 } 13 console.log('第一個for迴圈結束'); 14 console.log(i); 15 16 console.log('第二個for迴圈開始'); 17 18 for (; i > 0; i--) { 19 console.log(i); 20 } 21 console.log('第二個for迴圈結束'); 22 console.log(i); 23 </script> 24 </body> 25 </html>
你猜它的輸出是什麼?
為何迴圈結束後,i的值仍然存在呢?js迴圈代碼塊內部變數的作用域怎麼跑到外面來了?作為js小白的我,竟然會問這麼傻的問題。答案是,js沒有塊級作用域,像for、if、switch等控制結構形成的代碼塊內部聲明的變數其實都是全局變數。對於像c這種有塊級作用域的語言,在for語句執行完畢後,迴圈變數會被銷毀,下麵是c語言的for迴圈:
1 #include "stdio.h" 2 3 int main() { 4 for (int i = 0; i < 3; i++) { 5 printf("%d\n", i); 6 } 7 8 printf("%d\n", i); // 這裡編譯時會報錯 error: use of undeclared identifier 'i' 9 }
編譯時報錯如下:
而對於javascript這種沒有塊級作用域的語言,即使在for迴圈結束之後,迴圈變數i也是會存在於迴圈外部的“全局”當中,因為這個迴圈變數其實就是“全局變數”。這種描述不夠準確,javascript當中一個重要的概念是執行環境(execution context),簡稱環境。每個環境都有一個與之關聯的變數對象,環境中定義的所有變數和函數都保存在這個對象中。全局執行環境是最外圍的一個執行環境,比如在瀏覽器中,全局執行環境就是window對象,所以,所有的全局變數都是作為window對象的屬性和方法創建的。比如上面的迴圈變數i,應該可以訪問window.i:
下麵只寫出js代碼:
1 for (var i = 0; i < 3; i++) { 2 console.log(i); 3 } 4 console.log('第一個for迴圈結束'); 5 console.log(i); 6 console.log('訪問window.i'); 7 console.log(window.i); 8 9 console.log('第二個for迴圈開始'); 10 11 for (; i > 0; i--) { 12 console.log(i); 13 } 14 console.log('第二個for迴圈結束'); 15 console.log(i); 16 17 console.log('訪問window.i'); 18 console.log(window.i);
結果如下:
那麼,javascript中的局部變數該怎麼定義呢?(或者說如何定義一個變數,這個變數不會被影響到全局呢),答案是函數。js中除了全局執行環境外,還有一個執行環境:函數執行環境,也可以把它叫做局部執行環境。比如下麵這段代碼:
1 var j = 0; 2 function test() { 3 var j = 1; 4 return j; 5 } 6 7 console.log(j);
輸出結果為0,證明函數內部的j沒有污染到外部的j,函數內部的j只在函數的執行環境內被訪問。
我又想到php跟js一樣都是腳本語言,那麼php是不是也沒有塊級作用域呢?看下麵的代碼:
1 <?php 2 3 if (true) { 4 $i = 7; 5 } 6 echo $i , PHP_EOL; 7 8 for ($i = 0; $i < 3; $i++) { 9 echo $i , PHP_EOL; 10 } 11 echo '第一個for迴圈結束' , PHP_EOL; 12 echo $i , PHP_EOL; 13 14 echo '第二個for迴圈開始' , PHP_EOL; 15 for (; $i > 0; $i--) { 16 echo $i , PHP_EOL; 17 } 18 echo '第二個for迴圈結束' , PHP_EOL; 19 20 echo $i , PHP_EOL;
輸出結果如下:
嗯,沒錯,它也沒有塊級作用域。其實php手冊中很“含蓄”的表達了這一點:“變數的範圍即它定義的上下文背景(也就是它的生效範圍)。大部分的 PHP 變數只有一個單獨的範圍。這個單獨的範圍跨度同樣包含了 include 和 require 引入的文件。......在用戶自定義函數中,一個局部函數範圍將被引入。任何用於函數內部的變數按預設情況將被限制在局部函數範圍內。”
我又想到了php的for迴圈,我記得php手冊中對於foreach迴圈的描述中有這麼一段話“Warning:數組最後一個元素的 $value 引用在 foreach 迴圈之後仍會保留。建議使用 unset() 來將其銷毀。”,這個恐怕也是因為它沒有塊級作用域的原因,但手冊中for迴圈中沒有提到這一點,這有些奇怪。看來,php的水也很深。
其實,javascript的ES6規範中已經引入了let(還有const)變數標識符,利用let可以讓js的變數擁有塊級作用域,看下麵的代碼:
1 for (let i = 0; i < 3; i++) { 2 console.log(i); 3 } 4 console.log('第一個for迴圈結束'); 5 console.log(i);
輸出結果如下:
看來js正在變得越來越規範。
ps:js的執行環境、php的局部變數和全局變數,以後還要仔細學習下。
參考:
1,javascript高級程式設計
2,php手冊