在討論這次的主題之前,我們現在看一下指令碼優化的另一個問題,就是“優化難度”。在這裡我所說的“優化難度”是指優化一張頁面時的修改難度。例如在前一片文章中,使用document.write來引入指令碼的話,其“優化難度”會非常的低——沒有任何副作用,不用修改其它任何程式碼。不過它的效果似乎還不太理想,因為僅僅優化了IE下的體驗,在FireFox裡卻沒有任何作用。
很可惜,我回想了幾乎所有的優化方式,再也沒有找到優化難度如此低的做法了。對於其它的方式,我們都必須在頁面的別處進行修改,優化效果越好,修改量越大。對於這些優化方式,我們就必須編寫合適的元件,將一些邏輯封裝起來。這樣可以在一定程度上方便使用,降低優化難度。
比較document.write與defer
那麼這又何document.write或者defer有什麼關係?且聽我慢慢道來。
<script />的defer屬性在標準裡的定義是這樣的:
When set, this boolean attribute provides a hint to the user agent that the script is not going to generate any document content (e.g., no "document.write" in javascript) and thus, the user agent can continue parsing and rendering.
我們當時遇到JS無法並行下載的原因就是瀏覽器認為在指令碼中可能會輸出HTML內容。defer屬性的作用就是告訴瀏覽器,指令碼里不會輸出任何資訊。果然,當我們在IE裡使用defer屬性時,指令碼沒有被阻塞,其效果和document.write一樣。不過在FireFox裡依舊不行,這樣的實現實在讓人費解。
都說FireFox標準,看來在細節上也不盡然。
那麼為什麼我們在之前使用了document.write而不是defer屬性呢?兩者效果相同,但是明顯使用defer屬性更加直觀啊。
defer屬性使用起來的確直觀和方便。不過,效果真的相同嗎?我們可以通過以下的例子試試看。
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> <script type="text/javascript" language="javascript"> document.write( '<script type="text/javascript" language="javascript"' + ' src="Scripts.ashx?a"><' + '/script>'); document.write( '<script type="text/javascript" language="javascript"' + ' src="Scripts.ashx?b"><' + '/script>'); document.write( '<script type="text/javascript" language="javascript"' + ' src="Scripts.ashx?c"><' + '/script>'); </script> </head> <body> <input type="button" value="Click" /> <script type="text/javascript" language="javascript" src="Scripts.ashx?a"> alert('Hello World'); </script> </body> </html>
然後再使用<script defer="defer"></script>的方式引入一下。開啟兩個頁面進行比較就會發現,如果使用document.write的話,在指令碼載入完畢之前按鈕不會顯示,也不會出現提示框;而如果使用defer屬性的話,按鈕就立即出現了,也會馬上出現提示。
這可麻煩了。如果頁面上的元素過早出現,使用者在指令碼載入完之前進行操作是否會有問題?如果頁面裡存在直接執行的指令碼(如上例的alert呼叫),在指令碼檔案載入完之前是否能夠執行?如果上面兩個問題的答案有任何一個是肯定的話,那麼恭喜您,使用defer屬性就會造成錯誤了。而且這個問題的解決方案實在不太容易找到,這大大增加了“優化難度”。
而且更為關鍵的是,FireFox同樣不支援defer屬性的效果。這直接導致了defer屬性全面落後於使用document.write的優化方式。既然這樣,我們為什麼要用它?事實上defer屬性用的實在不多,這是個非常典型的的“雞肋” 特性。
那麼,哪裡有使用defer屬性的應用呢?我想應該是有的吧,雖然我不知道。
突破兩個連線的限制
在上一片文章裡我們可以看到,雖然document.write方法可以讓指令碼檔案並行載入,但是它依舊受到瀏覽器的限制。根據HTTP協議的標準,對於同一個Domain,只能同時存在兩個連線。在這點上,親愛的瀏覽器們都乖乖的實現了。我們如果想要突破這種限制,就要增加域名。不過其實瀏覽器判斷域名的方式是非常嚴格的,同一域名下的子域名,同一域名不同埠,都不算相同。一般來說,使用子域名來增加並行載入的連線數是比較常用的做法。
應該已經有不少朋友知道這個方法,它的應用實在太普遍了。不過請注意,請求任意資源時都會建立連線,瀏覽器對於某一域名的連線並不區分其作用。因此,無論下載圖片,CSS檔案,JavaScript檔案,或者是XMLHttpRequest物件建立的AJAX連線,都屬於“兩個連線”之內,在優化時往往需要注意這一點。另外,一個瀏覽器裡同時建立的連線數也不是越多越好,根據實驗資料顯示,瀏覽器可以同時建立6到7連線最為合適。因此,我們使用3到4個子域名是比較妥當的。
我們現在就來看一下使用效果。在開發時要出現這個效果,我們可以修改C:\WINDOWS\system32\drivers\etc\hosts檔案來設定本地的DNS對映。如下:
127.0.0.1 www.test.com 127.0.0.1 sub0.test.com 127.0.0.1 sub1.test.com 127.0.0.1 sub2.test.com 127.0.0.1 sub3.test.com 127.0.0.1 sub4.test.com 127.0.0.1 sub5.test.com
我們可以多加一些子域名,方便以後使用。
接下來我們就可以在頁面裡從多個不同的子域名載入指令碼檔案,如下:
<html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page</title> <script type="text/javascript" language="javascript"> document.write('<script type="text/javascript" language="javascript"' + ' src="http://sub0.test.com/Scripts.ashx?a"><' + '/script>'); document.write('<script type="text/javascript" language="javascript"' + ' src="http://sub0.test.com/Scripts.ashx?b"><' + '/script>'); document.write('<script type="text/javascript" language="javascript"' + ' src="http://sub1.test.com/Scripts.ashx?c"><' + '/script>'); document.write('<script type="text/javascript" language="javascript"' + ' src="http://sub1.test.com/Scripts.ashx?d"><' + '/script>'); document.write('<script type="text/javascript" language="javascript"' + ' src="http://sub2.test.com/Scripts.ashx?e"><' + '/script>'); </script> </head> <body> ... </body> </html>
在瀏覽器開啟頁面試試看?還記得當初我們載入頁面用了多少時間嗎?8秒多!而現在已經能夠在不到2秒內載入完畢了(如圖2)。
圖8:使用多個子域名進行並行載入
可惜我們還要優化FireFox瀏覽器裡的情況,下次我們就來討論這個問題。接下來的優化方案會有一定的難度,不過只要我們利用得當,將會大大提高Perceived Performance。