首先,我想說一下Javascript的裝載和執行。通常來說,瀏覽器對於Javascript的運作有兩大功能:1)載入後馬上執行,2)執行時會阻塞頁面後續的內容(包括頁面的渲染、其它資源的下載)。於是,如果有多個js檔案被引入,那麼對於瀏覽器來說,這些js檔案被串列地載入,並且依序執行。
因為javascript可能會來操作HTML文檔的DOM樹,所以,瀏覽器一般都不會像並行下載css文件並行下載js文件,因為這是js文件的特殊性造成的。所以,如果你的javascript想操作後面的DOM元素,基本上來說,瀏覽器都會報錯說物件找不到。因為Javascript執行時,後面的HTML被阻塞住了,DOM樹時還沒有後面的DOM結點。所以程序也就報錯了。
傳統的方式
所以,當你在程式碼中寫下如下的程式碼:
<scripttype="text/javascript" src="http://coolshell.cn/asyncjs/alert.js"></script>
基本上來說,head裡的 <script>標籤會阻塞後續資源的載入以及整個頁面的生成。我專門做了一個範例你可以看看:範例一。 注意:我的alert.js中只有一句話:alert(“hello world”) ,這更容易讓你看到javascript是怎麼阻塞後面的東西的。 </script>
所以,你知道為什麼有很多網站把javascript放在網頁的最後面了,要嘛就是動用了window.onload或是docmuemt ready之類的事件。
另外,因為絕大多數的Javascript程式碼並不需要等頁面,所以,我們非同步載入的功能。那我們要怎麼異步載入呢?
document.write方式
於是,你可能以為document.write()這種方式能夠解決不阻塞的方式。你當然會覺得,document.write了的<script>標籤後就可以執行後面的東西去了,這沒錯。對於在同一個script標籤裡的Javascript的程式碼來說,是這樣的,但是對於整個頁面來說,這個還是會阻塞。 以下是一段測試程式碼:</script>
<scripttype="text/javascript"language="javascript"> function loadjs(script_filename) { document.write('<' + 'script language="javascript" type="text/javascript"'); document.write(' src="' + script_filename + '">'); document.write('<'+'/script'+'>'); alert("loadjs() exit..."); } var script = 'http://coolshell.cn/asyncjs/alert.js'; loadjs(script); alert("loadjs() finished!"); </script> <scripttype="text/javascript"language="javascript"> alert("another block"); </script>
你覺得alert的順序是什麼?你可以在不同的瀏覽器裡試試看。這裡的想關的測試頁面:範例二。
script的defer和async屬性
IE自從IE6就支援defer標籤,如:
<scriptdefertype="text/javascript"src="./alert.js"> </script>
對於IE來說,這個標籤裝載IE並行下載js文件,並且把其執行hold到了整個DOM. (DOMContentLoaded),多個defer的<script>在執行時也會按照其出現的順序來運作。最重要的是<script>被加上defer後,其不會阻塞後續DOM的渲染。但因為這個defer只是IE專用,所以一般用得比較少。 </script>
而我們標準的的HTML5也加入了一個非同步載入javascript的屬性:async,無論你對它賦什麼樣的值,只要它出現,它就開始非同步載入js檔案。但是, async的非同步載入會有一個比較嚴重的問題,那就是它忠實地實踐著「載入後馬上執行」這條軍規,所以,雖然它並不阻塞頁面的渲染,但是你也無法控制他執行的次序和時機。你可以看看這個範例去感受一下。
支援 async標籤的瀏覽器是:Firefox3.6+,Chrome 8.0+,Safari 5.0+,IE 10+,Opera還不支援(來自這裡)所以這個方法也不是太好。因為並不是所有的瀏覽器你都能行。
動態建立DOM方式
這種方式可能是用得最多的了。
functionloadjs(script_filename) { varscript = document.createElement('script'); script.setAttribute('type','text/javascript'); script.setAttribute('src', script_filename); script.setAttribute('id','coolshell_script_id'); script_id = document.getElementById('coolshell_script_id'); if(script_id){ document.getElementsByTagName('head')[0].removeChild(script_id); } document.getElementsByTagName('head')[0].appendChild(script); } varscript ='http://coolshell.cn/asyncjs/alert.js'; loadjs(script);
這個方式幾乎成了標準的非同步載入js檔案的方式,這個方式的示範請參考:範例三。這方式也被玩出了JSONP的東東,也就是我可以為script的src指定某個後台的腳本(如PHP),而這個PHP回傳一個javascript函數,其參數是一個json的字串,回傳呼叫我們的預先定義好的javascript的函數。你可以看一下這個例子:t.js (這個例子是我之前在微博徵集的一個異步ajax調用的小例子)
按需異步載入js
上面那個DOM方式的例子解決了異步載入Javascript的問題,但沒有解決我們想讓他以我們指定的時機來執行的問題。所以,我們只需要把上面那個DOM方式綁到某個事件上就可以了。
例如:
綁在window.load事件上——範例四
你一定要比較一下範例四和範例三在執行上有什麼不同,我在這兩個範例中都專門用了個程式碼高亮的javascript,看看那個程式碼高亮的的腳本的執行和我的alert.js的執行的情況,你就知道不同了)
window.load = loadjs("http://coolshell.cn/asyncjs/alert.js")
綁在特定的事件上——示例五
<p style="cursor: pointer"onclick="LoadJS()">Click to load alert.js </p>
这个示例很简单了。当你点击某个DOM元素,才会真正载入我们的alert.js。
更多
但是,绑定在某个特定事件上这个事似乎又过了一点,因为只有在点击的时候才会去真正的下载js,这又会太慢了了。好了,到这里,要抛出我们的终极问题——我们想要异步地把js文件下载到用户的本地,但是不执行,仅当在我们想要执行的时候去执行。
要是我们有下面这样的方式就好了:
varscript = document.createElement("script"); script.noexecute =true; script.src ="alert.js"; document.body.appendChild(script); //后面我们可以这么干 script.execute();
可惜的是,这只是一个美丽的梦想,今天我们的Javascript还比较原始,这个“JS梦”还没有实现呢。
所以,我们的程序员只能使用hack的方式来搞。
有的程序员使用了非标准的script的type来cache javascript。如:
<scripttype=cache/scriptsrc="./alert.js"></script>
因为”cache/script”,这个东西根本就不能被浏览器解析,所以浏览器也就不能把alert.js当javascript去执行,但是他又要去下载js文件,所以就可以搞定了。可惜的是,webkit严格符从了HTML的标准——对于这种不认识的东西,直接删除,什么也不干。于是,我们的梦又破了。
所以,我们需要再hack一下,就像N多年前玩preload图片那样,我们可以动用object标签(也可以动用iframe标签),于是我们有下面这样的代码:
functioncachejs(script_filename){ varcache = document.createElement('object'); cache.data = script_filename; cache.id ="coolshell_script_cache_id"; cache.width = 0; cache.height = 0; document.body.appendChild(cache); }
然后,我们在的最后调用一下这个函数。请参看一下相关的示例:示例六
在Chrome下按 Ctrl+Shit+I,切换到network页,你就可以看到下载了alert.js但是没有执行,然后我们再用示例五的方式,因为浏览器端有缓存了,不会再从服务器上下载alert.js了。所以,就能保证执行速度了。
关于这种preload这种东西你应该不会陌生了。你还可以使用Ajax的方式,如:
varxhr =newXMLHttpRequest(); xhr.open('GET','new.js'); xhr.send('');
到这里我就不再多说了,也不给示例了,大家可以自己试试去。
最后再提两个js,一个是ControlJS,一个叫HeadJS,专门用来做异步load javascript文件的。