JavaScript引擎的工作機制比較深奧,它屬於底層行為。JavaScript代碼執行順序就比較形象了,用戶可以直觀感受到這種執行順序,當然,JavaScript代碼的執行順序是比較複雜的。HTML文檔在瀏覽器中的解析過程是這樣的:瀏覽器按著文檔流從上到下逐步解析頁面結構和信息。JavaScript代碼作為嵌入的腳本也算做HTML文檔的組成部分,因此,JavaScript代碼在裝載時的執行順序也是根據腳本標籤<script>的出現順序來確定的。
例如,瀏覽下面文檔頁面,所有代碼都是從上到下被逐步解析的。
<script>
alert(\"頂部腳本\");
</script>
<html><head>
<script>
alert(\"頭部腳本\");
</script>
<title></title>
</head>
<body>
<script>
alert(\"頁面腳本\");
</script>
</body></html>
<script>
alert(\"底部腳本\");
</script>
對於通過腳本標籤<script>的src屬性導入的外部JavaScript文件腳本,它也將按照其語句出現的順序來執行,而且執行過程是文檔裝載的一部分,不會因為是外部JavaScript文件而延期執行。例如,把上面文檔中的頭部和主體區域的腳本移到外部JavaScript文件中,然後通過src屬性導入進來,繼續預覽頁面文檔,會看到相同的執行順序。
<script>
alert(\"頂部腳本\");
</script>
<html>
<head>
<script src=\"head.js\"></script>
<title></title>
</head>
<body>
<script src=\"body.js\"></script>
</body>
</html>
<script>
alert(\"底部腳本\");
</script>
當JavaScript引擎解析腳本時,它會在預編譯期對所有聲明的變量和函數進行處理,因此,就會出現當JavaScript解釋器執行下面腳本時不會報錯的情況。
alert(a);//undefined
var a=1;
alert(a);//1
由於變量聲明是在預編譯期進行的,因此,在執行期間,變量對所有代碼來說都是可見的。執行上面的代碼,提示的值是undefined,而不是1,這是因為變量初始化過程發生在執行期,而不是預編譯期。在執行期,JavaScript解釋器是按著代碼先後順序進行解析的,如果在前面代碼行中沒有為變量賦值,那麼JavaScript解釋器會使用默認值undefined。由於在第二行中為變量a賦值了,因此在第三行代碼中會提示變量a的值為1,而不是undefined。
同理,在下面示例中,在函數聲明前調用函數也是合法的,並能夠被正確解析,因此,返回值為1。
f;//1
function f{
alert(1);
}
但是,如果按下面方式定義函數,那麼JavaScript解釋器會提示語法錯誤。
f;//返回語法錯誤
var f=function{
alert(1);
}
在上面代碼中定義的函數僅作為值賦值給變量f,因此,在預編譯期,JavaScript解釋器只能為聲明變量f進行處理,而對於變量f的值,只能等到執行期按順序進行賦值,自然就會出現語法錯誤,提示找不到對像f。
雖然變量和函數聲明可以在文檔任意位置,但是良好的習慣應該是在所有JavaScript代碼之前聲明全局變量和函數,並且對變量進行初始化賦值。在函數內部也是先聲明變量,然後再引用。
同時,JavaScript是分塊執行代碼的。所謂代碼塊就是使用<script>標籤分隔的代碼段。例如,下面兩個<script>標籤分別代表兩個JavaScript代碼塊。
<script>
//JavaScript代碼塊1
var a=1;
</script>
<script>
//JavaScript代碼塊2
function f{
alert(1);
}
</script>
JavaScript在執行腳本時是按塊來執行的。瀏覽器在解析HTML文檔流時,如果遇到一個<script>標籤,則JavaScript會等到這個代碼塊都加載完後再對代碼塊進行預編譯,然後再執行。執行完畢後,瀏覽器會繼續解析下面的HTML文檔流,同時JavaScript也準備好處理下一個代碼塊。
由於JavaScript是按塊執行的,因此在一個JavaScript塊中調用後面塊中聲明的變量或函數就會提示語法錯誤。例如,當JavaScript解釋器執行下面代碼時就會提示語法錯誤,顯示變量a未定義,對像f找不到。
<script>
//JavaScript代碼塊1
alert(a);
f;
</script>
<script>
//JavaScript代碼塊2
var a=1;
function f{
alert(1);
}
</script>
雖然JavaScript是按塊執行的,但是不同塊都屬於同一個全局作用域,也就是說,塊之間的變量和函數是可以共享的。
由於JavaScript是按塊處理代碼,同時又遵循HTML文檔流的解析順序,因此在上面示例中會看到語法錯誤。但是,在文檔流加載完畢後再次訪問就不會出現這樣的錯誤。例如,把訪問第2個代碼塊中的變量和函數的代碼放在頁面初始化事件函數中,這樣就不會出現語法錯誤了。
<script>
//JavaScript代碼塊1
window.onload=function{//頁面初始化事件處理函數
alert(a);
f;
}
</script>
<script>
//JavaScript代碼塊2
var a=1;
function f{
alert(1);
}
</script>
為了安全起見,一般在頁面初始化完畢之後才允許JavaScript代碼執行,這樣就可以避免網速對JavaScript執行的影響。同時,也避開了HTML文檔流對JavaScript執行的限制。
如果一個頁面中存在多個window.onload事件處理函數,那麼只有最後一個才是有效的,為了解決這個問題,可以把所有腳本或調用函數都放在同一個onload事件處理函數中。
window.onload=function{
f1;
f2;
f3;
}
通過這種方式還可以改變函數的執行順序,方法是簡單地調整onload事件處理函數中調用函數的排列順序。
除了頁面初始化事件外,還可以通過各種交互事件來改變JavaScript代碼的執行順序,如鼠標事件、鍵盤事件,以及時鐘觸發器等方法。
在JavaScript開發中,經常會使用document對象的write方法輸出JavaScript腳本。
document.write(\'<script type=\"text/javascript\">\');
document.write(\'f;\');
document.write(\'function f{\');
document.write(\'alert(1);\');
document.write(\'}\');
document.write(\'</script>\');
運行以上代碼,document.write方法先把輸出的腳本字符串寫入到腳本所在的文檔位置,瀏覽器在解析完document.write所在文檔的內容後,繼續解析document.write輸出的內容,然後按順序解析後面的HTML文檔。也就是說,JavaScript腳本輸出的代碼字符串會在輸出後馬上被執行。
使用document.write方法輸出的JavaScript腳本字符串必須放在同時輸出的<script>標籤中,否則JavaScript解釋器會因為不能識別這些合法的JavaScript代碼而作為普通的字符串顯示在頁面文檔中。例如,下面的代碼就會把JavaScript代碼顯示出來,而不是執行它。
document.write(\'f;\');
document.write(\'function f{\');
document.write(\'alert(1);\');
document.write(\');\');
但是,通過document.write方法輸出並執行腳本也存在一定的風險,因為不同JavaScript引擎對其執行順序不同,同時不同瀏覽器在解析時也會出現Bug。
❑找不到通過document.write方法導入的外部JavaScript文件中聲明的變量或函數,例如:
document.write(\'<script type=\"text/javascript\"src=\"test.js\"></script>\');
document.write(\'<script type=\"text/javascript\">\');
document.write(\'alert(n);\');//IE提示找不到變量n
document.write(\'</script>\');
alert(n+1);//所有瀏覽器都會提示找不到變量n
外部JavaScript文件(test.js)的代碼如下:
var n=1;
分別在不同瀏覽器中進行測試,均會發現提示語法錯誤,找不到變量n。也就是說,如果在JavaScript代碼塊中訪問本代碼塊使用document.write方法輸出的腳本導入的外部JavaScript文件所包含的變量,會顯示語法錯誤。同時,如果在IE中,不僅在腳本中,在輸出的腳本中也會提示找不到輸出的導入外部JavaScript文件的變量(表述有點長且有點拗口,不懂的讀者嘗試運行上面代碼即可明白)。
❑不同JavaScript引擎對輸出的外部導入腳本的執行順序略有不同,例如:
<script type=」text/javascript」>
document.write(\'<script type=\"text/javascript\"src=\"test1.js\"></script>\');
document.write(\'<script type=\"text/javascript\">\');
document.write(\'alert(2);\')
document.write(\'alert(n+2);\');
document.write(\'</script>\');
</script>
<script type=\"text/javascript\">
alert(n+3);
</script>
外部JavaScript文件(test1.js)的代碼如下:
var n=1;
alert(n);
在IE中的執行順序如圖9.5所示。
圖 9.5 在IE中的執行順序和提示的語法錯誤在符合DOM標準的瀏覽器中的執行順序與IE不同,並且沒有語法錯誤。在Firefox瀏覽器中的執行順序如圖9.6所示。
圖 9.6 在Firefox瀏覽器中的執行順序可以把凡是使用輸出腳本導入的外部文件,都放在獨立的代碼塊中,這樣根據上面介紹的JavaScript代碼塊執行順序,就可以解決不同瀏覽器的不同執行順序的問題,以及可能存在的Bug。例如,針對上面示例,可以這樣設計:
<script type=\"text/javascript\">
document.write(\'<script type=\"text/javascript\"src=\"test1.js\"></script>\');
</script>
<script type=\"text/javascript\">
document.write(\'<script type=\"text/javascript\">\');
document.write(\'alert(2);\');//提示2
document.write(\'alert(n+2);\');//提示3
document.write(\'</script>\');
alert(n+3);//提示4
</script>
<script type=\"text/javascript\">
alert(n+4);//提示5
</script>
這樣,在不同瀏覽器中都能夠按順序執行上面代碼,並且輸出順序都是1、2、3、4和5。存在問題的原因:輸出導入的腳本與當前JavaScript代碼塊之間的矛盾。單獨輸出就不會發生衝突了。