一、在HTML中嵌入Javasript的方法
- 直接在Javascript代碼放在標記對<script>和</script>之間
- 由<script />標記的src屬性制定外部的js文件
- 放在事件處理程序中,比如:<p onclick=”alert(‘我是由onclick事件執行的Javascript’)”>點擊我</p>
- 作為URL的主體,這個URL使用特殊的Javascript:協議,比如:<a href=”javascript:alert(‘我是由javascript:協議執行的javascript’)”>點擊我</a>
- 利用javascript本身的document.write()方法寫入新的javascript代碼
- 利用Ajax異步獲取javascript代碼,然后執行
第3種和第4種方法寫入的Javascript需要觸發才能執行,所以除非特別設置,否則頁面加載時不會執行。
二、Javascript在頁面的執行順序
- 頁面上的Javascript代碼是HTML文檔的一部分,所以Javascript在頁面裝載時執行的順序就是其引入標記<script />的出現順序, <script />標記里面的或者通過src引入的外部JS,都是按照其語句出現的順序執行,而且執行過程是文檔裝載的一部分。
- 每個腳本定義的全局變量和函數,都可以被后面執行的腳本所調用。
- 變量的調用,必須是前面已經聲明,否則獲取的變量值是undefined。
1 <script type="text/javascript">//<![CDATA[ 2 alert(tmp); //輸出 undefined 3 var tmp = '111'; 4 alert(tmp); //輸出 111 5 //]]> 6 </script>
- 同一段腳本,函數定義可以出現在函數調用的后面,但是如果是分別在兩段代碼,且函數調用在第一段代碼中,則會報函數未定義錯誤。
1 <script type="text/javascript">//<![CDATA[ 2 test(); //瀏覽器報錯 3 //]]> 4 </script> 5 <script type="text/javascript">//<![CDATA[ 6 test(); //輸出 fun! 7 function test(){alert('fun!');} 8 //]]> 9 </script>
- document.write()會把輸出寫入到腳本文檔所在的位置,瀏覽器解析完documemt.write()所在文檔內容后,繼續解析document.write()輸出的內容,然后在繼續解析HTML文檔。
01 <script type="text/javascript">//<![CDATA[ 02 document.write('<script type="text/javascript" src="test.js"><\/script>'); 03 document.write('<script type="text/javascript">'); 04 document.write('alert("222");') 05 document.write('alert("變量保存值" + tmpStr);'); 06 document.write('<\/script>'); 07 //]]> 08 </script> 09 <script type="text/javascript">//<![CDATA[ 10 alert("333"); 11 //]]> 12 </script>
test.js的內容是:
1 var tmpStr = '111'; 2 alert(tmpStr);
- 在Firefox和Opera中的彈出值的順序是:111、222、變量保存值111、333
- 在IE中彈出值的順序是:222、111、333, 同時瀏覽器報錯:tmpStr未定義
原因可能是IE在document.write時,並未等待加載SRC中的Javascript代碼完畢后,才執行下一行,所以導致222先彈出,並且執行到document.write(‘document.write(“變量保存值” + tmpStr)’)調用tmpStr時,tmpStr並未定義,從而報錯。
要解決這個問題,可以利用HTML解析時解析完一個HTML標簽,再執行下一個的原理,把代碼拆分來實現(上面第一段js分拆成兩段):
01 <script type="text/javascript">//<![CDATA[ 02 document.write('<script type="text/javascript" src="test.js"><\/script>'); 03 //]]> 04 </script> 05 <script type="text/javascript">//<![CDATA[ 06 document.write('<script type="text/javascript">'); 07 document.write('alert("222");') 08 document.write('alert("變量保存值" + tmpStr);'); 09 document.write('<\/script>'); 10 //]]> 11 </script> 12 <script type="text/javascript">//<![CDATA[ 13 alert('333'); 14 //]]> 15 </script>
這樣IE下和其他瀏覽器輸出值的順序一致了:111、222、變量保存值111、333。
三、如何改變Javascript在頁面的執行順序
- 利用window.onload
1 <script type="text/javascript">//<![CDATA[ 2 window.onload = func1; 3 function func1(){alert('111');} 4 alert('222'); 5 //]]> 6 </script>
輸出值順序是 222、111。
需要注意的是,如果存在多個winodws.onload的話,只有最后一個生效,解決的辦法是:
1 window.onload = function(){f();f1();f2();.....}
利用2級DOM事件類型
1 if(document.addEventListener){ 2 window.addEventListener('load',f,false); 3 window.addEventListener('load',f1,false); 4 ... 5 }else{ 6 window.attachEvent('onload',f); 7 window.attachEvent('onload',f1); 8 ... 9 }
- IE中可以利用defer,defer作用是把代碼加載下來,並不立即執行,等文檔裝載完畢之后再執行,有點類似window.onload,但是沒有window.onload那樣的局限性,可以重復使用,但是只在IE中有效,所以上面的例子可以修改成為
01 <script type="text/javascript">//<![CDATA[ 02 document.write('<script type="text/javascript" src="test.js"><\/script>'); 03 document.write('<script type="text/javascript" defer="defer">'); 04 document.write('alert("222");') 05 document.write('alert("變量保存值" + tmpStr);'); 06 document.write('<\/script>'); 07 //]]> 08 </script> 09 <script type="text/javascript">//<![CDATA[ 10 alert("333"); 11 //]]> 12 </script>
這樣IE就不報錯了,輸出值的順序變成:111、333、222、變量保存值111
當HTML解析器遇到一個腳本,它必須按常規終止對文檔的解析並等待腳本執行。為了解決這個問題HTML4標准定義了defer。通過defer來提示瀏覽器可以繼續解析HTML文檔,並延遲執行腳本。這種延遲在腳本從外部文件載入時非常有用,讓瀏覽器不必等待外部文件全部載入之后才繼續執行,能有效的提高性能。IE是目前唯一支持defer屬性的瀏覽器,但IE並沒有正確的實現了defer屬性,因為延遲的腳本總是被延遲,直到文檔結束,而不是只延遲到下一個非延遲的腳本。這意味着,IE中延遲的腳本的執行順序相當混亂,並且不能定義任何后面非延遲腳本並須的函數和變量。在IE中所有的defer的腳本執行時間應該都是HTML文檔樹建立以后,window.onload之前。
- 利用Ajax。
因為xmlhttpRequest能判斷外部文檔加載的狀態,所以能夠改變代碼的加載順序。
4、一些注意事項:
以上所講的deffer看似用起來沒什么問題,但是實際上無法兼容Mozilla。所以通常我們還是要借助window.onload來實現。但是,之后就沒有問題了么?假設頁面dom里有一張圖片,像這樣:
1 <img src="picture.jpg" >
而這張圖片又非常之大(例如30MB,這個數字雖然有點極端,但真的在有些網站出現過!),那么在dom加載完畢之前,js是無法執行的。問題就在於假設dom提供了用戶交互的功能。例如按鈕,輸入表單等,這個時候他們已經是被呈現了的,因此就很有可能產生無效的用戶行為。
我們不能指望用戶像我們預期的那樣等待頁面顯示加載完畢后再發生動作,而要把用戶考慮成隨時隨刻會到處亂點的朋友。
這個問題又如何解決呢?既然我們需要頁面結構輸出后執行js,我們不妨把js入口函數定義在頁面最下面好了。
1 <head>
2 <script src="x.js" type="text/javascript"></script>
3 </head>
4 <body>
5 ......
6 <img src="picture.jpg" >
7 <script type="text/javascript">init();</script>
8 </body>
這樣就達到我們的目的了,頁面結構輸出完畢后就執行js,不用考慮圖片的加載。
但是在文檔末尾嵌入一條js腳本,畢竟容易被忽略,把關鍵的程序入口放在這種渺小的角落,總覺得不太合適。那有什么預留退路的方法沒有呢?
我們可以把結尾的腳本稍微修改一下:
1 <head>
2 <script src="x.js" type="text/javascript"></script>
3 </head>
4 <body>
5 ......
6 <img src="picture.jpg" >
7 <script type="text/javascript">window.onload();</script>
8 </body>
而在js里預先把入口定義給onload事件:
1 window.onload = function() { 2 alert("load over"); 3 }
這時候頁面結構加載完畢后就會調用onload函數,而即使漏寫了dom里的onload入口,js自身里的onload定義也會在頁面加載完畢后執行,這樣退路就留出來了。
不過這時候有個問題,onload事件會執行兩次,可以在js的onload實現里解決這個問題,改成這樣:
1 var flag = false; 2 window.onload = function() { 3 if (flag) {return;} 4 flag= true; 5 alert("load over"); 6 }
這樣似乎已經解決我們所有的問題了,不過仍然有些小遺憾,因為最后一行代碼,致使行為與結構沒有分離開來,要 unobtrusive 就要 unobtrusive 的徹底,為了達到完美的分離,還有很大的討論空間。
而對於js文件內部的onload事件,我們還可以參考 Simon Willison 的addLoadEvent函數來優化:
01 function addLoadEvent(func) { 02 var oldonload = window.onload; 03 if (typeof window.onload != 'function') { 04 window.onload = func; 05 } else { 06 window.onload = function() { 07 if (oldonload) { 08 oldonload(); 09 } 10 func(); 11 } 12 } 13 }
然后,我們就可以在js里肆無忌憚地不停地將各個不同的函數添加到onload事件響應中了:
1 addLoadEvent(funcA); 2 addLoadEvent(funcB); 3 addLoadEvent(funcC);
當然,同一個js里設置多個onload響應函數其實沒什么必要,我們完全可以把funcA、funcB、funcC封裝在一個函數里add,addLoadEvent函數,更理想的狀態是為頁面動態調用的多個js文件添加入口。