JavaScript的效能優化:載入和執行
隨著Web2.0技術的不斷推廣,越來越多的應用使用 JavaScript 技術在客戶端進行處理,從而使JavaScript在瀏覽器中的效能成為開發者所面臨的最重要的可用性問題。而這個問題又因JavaScript的阻塞特性變的複雜,也就是說當瀏覽器在執行JavaScript程式碼時,不能同時做其他任何事情。本文詳細介紹瞭如何正確的載入和執行 JavaScript程式碼,從而提高其在瀏覽器中的效能。
無論當前JavaScript程式碼是內嵌還是在外鏈檔案中,頁面的下載和渲染都必須停下來等待指令碼執行完成。JavaScript執行過程耗時越久,瀏覽器等待響應使用者輸入的時間就越長。瀏覽器在下載和執行指令碼時出現阻塞的原因在於,指令碼可能會改變頁面或JavaScript的名稱空間,它們對後面頁面內容造成影響。一個典型的例子就是在頁面中使用document.write()
- <html>
- <head>
- <title>Source Example</title>
- </head>
- <body>
- <p>
- <script type="text/javascript">
- document.write("Today is " + (new Date()).toDateString());
- </script>
- </p>
-
</
- </html>
當瀏覽器遇到<script>標籤時,當前HTML頁面無從獲知JavaScript是否會向<p> 標籤新增內容,或引入其他元素,或甚至移除該標籤。因此,這時瀏覽器會停止處理頁面,先執行JavaScript程式碼,然後再繼續解析和渲染頁面。同樣的情況也發生在使用src屬性載入JavaScript的過程中,瀏覽器必須先花時間下載外鏈檔案中的程式碼,然後解析並執行它。在這個過程中,頁面渲染和使用者互動完全被阻塞了。
指令碼位置
HTML 4 規範指出 <script> 標籤可以放在 HTML 文件的<head>或<body>中,並允許出現多次。Web 開發人員一般習慣在 <head> 中載入外鏈的 JavaScript,接著用 <link> 標籤用來載入外鏈的 CSS 檔案或者其他頁面資訊。例如清單 2
清單 2 低效率指令碼位置示例
Js程式碼- <html>
- <head>
- <title>Source Example</title>
- <script type="text/javascript" src="script1.js"></script>
- <script type="text/javascript" src="script2.js"></script>
- <script type="text/javascript" src="script3.js"></script>
- <link rel="stylesheet" type="text/css" href="styles.css">
- </head>
- <body>
- <p>Hello world!</p>
- </body>
- </html>
然而這種常規的做法卻隱藏著嚴重的效能問題。在清單 2 的示例中,當瀏覽器解析到 <script> 標籤(第 4 行)時,瀏覽器會停止解析其後的內容,而優先下載指令碼檔案,並執行其中的程式碼,這意味著,其後的 styles.css 樣式檔案和<body>標籤都無法被載入,由於<body>標籤無法被載入,那麼頁面自然就無法渲染了。因此在該JavaScript程式碼完全執行完之前,頁面都是一片空白。圖 1 描述了頁面載入過程中指令碼和樣式檔案的下載過程。
圖 1 JavaScript 檔案的載入和執行阻塞其他檔案的下載
我們可以發現一個有趣的現象:第一個 JavaScript 檔案開始下載,與此同時阻塞了頁面其他檔案的下載。此外,從 script1.js 下載完成到 script2.js 開始下載前存在一個延時,這段時間正好是 script1.js 檔案的執行過程。每個檔案必須等到前一個檔案下載並執行完成才會開始下載。在這些檔案逐個下載過程中,使用者看到的是一片空白的頁面。
從 IE 8、Firefox 3.5、Safari 4 和 Chrome 2 開始都允許並行下載 JavaScript 檔案。這是個好訊息,因為<script>標籤在下載外部資源時不會阻塞其他<script>標籤。遺憾的是,JavaScript 下載過程仍然會阻塞其他資源的下載,比如樣式檔案和圖片。儘管指令碼的下載過程不會互相影響,但頁面仍然必須等待所有 JavaScript 程式碼下載並執行完成才能繼續。因此,儘管最新的瀏覽器通過允許並行下載提高了效能,但問題尚未完全解決,指令碼阻塞仍然是一個問題。
由於指令碼會阻塞頁面其他資源的下載,因此推薦將所有<script>標籤儘可能放到<body>標籤的底部,以儘量減少對整個頁面下載的影響。例如清單 3
清單 3 推薦的程式碼放置位置示例
Js程式碼- <html>
- <head>
- <title>Source Example</title>
- <link rel="stylesheet" type="text/css" href="styles.css">
- </head>
- <body>
- <p>Hello world!</p>
- <!-- Example of efficient script positioning -->
- <script type="text/javascript" src="script1.js"></script>
- <script type="text/javascript" src="script2.js"></script>
- <script type="text/javascript" src="script3.js"></script>
- </body>
- </html>
這段程式碼展示了在 HTML 文件中放置<script>標籤的推薦位置。儘管指令碼下載會阻塞另一個指令碼,但是頁面的大部分內容都已經下載完成並顯示給了使用者,因此頁面下載不會顯得太慢。這是優化 JavaScript 的首要規則:將指令碼放在底部。
組織指令碼
由於每個<script>
標籤初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>
標籤數量有助於改善這一情況。這不僅針對外鏈指令碼,內嵌指令碼的數量同樣也要限制。瀏覽器在解析
HTML 頁面的過程中每遇到一個<script>
標籤,都會因執行指令碼而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體效能。
這個問題在處理外鏈 JavaScript 檔案時略有不同。考慮到 HTTP 請求會帶來額外的效能開銷,因此下載單個 100Kb 的檔案將比下載 5 個 20Kb 的檔案更快。也就是說,減少頁面中外鏈指令碼的數量將會改善效能。
通常一個大型網站或應用需要依賴數個 JavaScript 檔案。您可以把多個檔案合併成一個,這樣只需要引用一個<script>
標籤,就可以減少效能消耗。檔案合併的工作可通過離線的打包工具或者一些實時的線上服務來實現。
需要特別提醒的是,把一段內嵌指令碼放在引用外鏈樣式表的<link>
之後會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌指令碼在執行時能獲得最精確的樣式資訊。因此,建議不要把內嵌指令碼緊跟在<link>
標籤後面。
無阻塞的指令碼
減少 JavaScript 檔案大小並限制 HTTP 請求數在功能豐富的 Web 應用或大型網站上並不總是可行。Web 應用的功能越豐富,所需要的 JavaScript 程式碼就越多,儘管下載單個較大的 JavaScript 檔案只產生一次 HTTP 請求,卻會鎖死瀏覽器的一大段時間。為避免這種情況,需要通過一些特定的技術向頁面中逐步載入 JavaScript 檔案,這樣做在某種程度上來說不會阻塞瀏覽器。
無阻塞指令碼的祕訣在於,在頁面載入完成後才載入 JavaScript 程式碼。這就意味著在 window
物件的onload
事件觸發後再下載指令碼。有多種方式可以實現這一效果。
延遲載入指令碼
HTML 4 為<script>
標籤定義了一個擴充套件屬性:defer
。Defer
屬性指明本元素所含的指令碼不會修改
DOM,因此程式碼能安全地延遲執行。defer
屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支援,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer
屬性會被直接忽略,因此<script>
標籤會以預設的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支援的話,這仍然是個有用的解決方案。清單
4 是一個例子
清單 4 defer 屬性使用方法示例
Js程式碼- <script type="text/javascript" src="script1.js" defer></script>
帶有defer
屬性的<script>
標籤可以放置在文件的任何位置。對應的
JavaScript 檔案將在頁面解析到<script>
標籤時開始下載,但不會執行,直到 DOM 載入完成,即onload
事件觸發前才會被執行。當一個帶有 defer
屬性的
JavaScript 檔案下載時,它不會阻塞瀏覽器的其他程序,因此這類檔案可以與其他資原始檔一起並行下載。
任何帶有 defer
屬性的<script>
元素在
DOM 完成載入之前都不會被執行,無論內嵌或者是外鏈指令碼都是如此。清單 5 的例子展示了defer
屬性如何影響指令碼行為:
清單 5 defer 屬性對指令碼行為的影響
Js程式碼- <html>
- <head>
- <title>Script Defer Example</title>
- </head>
- <body>
- <script type="text/javascript" defer>
- alert("defer");
- </script>
- <script type="text/javascript">
- alert("script");
- </script>
- <script type="text/javascript">
- window.onload = function(){
- alert("load");
- };
- </script>
- </body>
- </html>
這段程式碼在頁面處理過程中彈出三次對話方塊。不支援 defer
屬性的瀏覽器的彈出順序是:“defer”、“script”、“load”。而在支援 defer
屬性的瀏覽器上,彈出的順序則是:“script”、“defer”、“load”。請注意,帶有 defer
屬性的<script>
元素不是跟在第二個後面執行,而是在 onload
事件被觸發前被呼叫。
如果您的目標瀏覽器只包括 Internet Explorer 和 Firefox 3.5,那麼 defer
指令碼確實有用。如果您需要支援跨領域的多種瀏覽器,那麼還有更一致的實現方式。
HTML 5 為<script>
標籤定義了一個新的擴充套件屬性:async
。它的作用和 defer
一樣,能夠非同步地載入和執行指令碼,不因為載入指令碼而阻塞頁面的載入。但是有一點需要注意,在有 async
的情況下,JavaScript
指令碼一旦下載好了就會執行,所以很有可能不是按照原本的順序來執行的。如果 JavaScript 指令碼前後有依賴性,使用 async
就很有可能出現錯誤。
動態指令碼元素
文件物件模型(DOM)允許您使用 JavaScript 動態建立 HTML 的幾乎全部文件內容。<script>
元素與頁面其他元素一樣,可以非常容易地通過標準 DOM 函式建立:
清單 6 通過標準 DOM 函式建立<script>元素
Js程式碼- var script = document.createElement ("script");
- script.type = "text/javascript";
- script.src = "script1.js";
- document.getElementsByTagName("head")[0].appendChild(script);
新的<script>
元素載入 script1.js 原始檔。此檔案當元素新增到頁面之後立刻開始下載。此技術的重點在於:無論在何處啟動下載,檔案的下載和執行都不會阻塞其他頁面處理過程。您甚至可以將這些程式碼放在<head>
部分而不會對其餘部分的頁面程式碼造成影響(除了用於下載檔案的
HTTP 連線)。
當檔案使用動態指令碼節點下載時,返回的程式碼通常立即執行(除了 Firefox 和 Opera,他們將等待此前的所有動態指令碼節點執行完畢)。當指令碼是“自執行”型別時,這一機制執行正常,但是如果指令碼只包含供頁面其他指令碼呼叫呼叫的介面,則會帶來問題。這種情況下,您需要跟蹤指令碼下載完成並是否準備妥善。可以使用動態 <script>
節點發出事件得到相關資訊。
Firefox、Opera, Chorme 和 Safari 3+會在<script>
節點接收完成之後發出一個 onload
事件。您可以監聽這一事件,以得到指令碼準備好的通知:
清單 7 通過監聽 onload 事件載入 JavaScript 指令碼
Js程式碼- var script = document.createElement ("script")
- script.type = "text/javascript";
- //Firefox, Opera, Chrome, Safari 3+
- script.onload = function(){
- alert("Script loaded!");
- };
- script.src = "script1.js";
- document.getElementsByTagName("head")[0].appendChild(script);
Internet Explorer 支援另一種實現方式,它發出一個 readystatechange
事件。<script>
元素有一個readyState
屬性,它的值隨著下載外部檔案的過程而改變。readyState
有五種取值:
- “uninitialized”:預設狀態
- “loading”:下載開始
- “loaded”:下載完成
- “interactive”:下載完成但尚不可用
- “complete”:所有資料已經準備好
微軟文件上說,在<script>
元素的生命週期中,readyState
的這些取值不一定全部出現,但並沒有指出哪些取值總會被用到。實踐中,我們最感興趣的是“loaded”和“complete”狀態。Internet
Explorer 對這兩個 readyState
值所表示的最終狀態並不一致,有時<script>
元素會得到“loader”卻從不出現“complete”,但另外一些情況下出現“complete”而用不到“loaded”。最安全的辦法就是在readystatechange
事件中檢查這兩種狀態,並且當其中一種狀態出現時,刪除readystatechange
事件控制代碼(保證事件不會被處理兩次):
清單 8 通過檢查readyState狀態載入JavaScript指令碼
Js程式碼- var script = document.createElement("script")
- script.type = "text/javascript";
- //Internet Explorer
- script.onreadystatechange = function(){
- if (script.readyState == "loaded" || script.readyState == "complete"){
- script.onreadystatechange = null;
- alert("Script loaded.");
- }
- };
- script.src = "script1.js";
- document.getElementsByTagName("head")[0].appendChild(script);
大多數情況下,您希望呼叫一個函式就可以實現JavaScript檔案的動態載入。下面的函式封裝了標準實現和 IE 實現所需的功能:
清單 9 通過函式進行封裝
Js程式碼- function loadScript(url, callback){
- var script = document.createElement ("script")
- script.type = "text/javascript";
- if (script.readyState){ //IE
- script.onreadystatechange = function(){
- if (script.readyState == "loaded" || script.readyState == "complete"){
- script.onreadystatechange = null;
- callback();
- }
- };
- } else { //Others
- script.onload = function(){
- callback();
- };
- }
- script.src = url;
- document.getElementsByTagName("head")[0].appendChild(script);
- }
此函式接收兩個引數:JavaScript 檔案的 URL,和一個當 JavaScript 接收完成時觸發的回撥函式。屬性檢查用於決定監視哪種事件。最後一步,設定 src
屬性,並將<script>
元素新增至頁面。此loadScript()
函式使用方法如下:
清單 10 loadScript()函式使用方法
Js程式碼- loadScript("script1.js", function(){
- alert("File is loaded!");
- });
您可以在頁面中動態載入很多 JavaScript 檔案,但要注意,瀏覽器不保證檔案載入的順序。所有主流瀏覽器之中,只有 Firefox 和 Opera 保證指令碼按照您指定的順序執行。其他瀏覽器將按照伺服器返回它們的次序下載並執行不同的程式碼檔案。您可以將下載操作串聯在一起以保證他們的次序,如下:
清單 11 通過 loadScript()函式載入多個JavaScript指令碼
Js程式碼- loadScript("script1.js", function(){
- loadScript("script2.js", function(){
- loadScript("script3.js", function(){
- alert("All files are loaded!");
- });
- });
- });
此程式碼等待 script1.js 可用之後才開始載入 script2.js,等 script2.js 可用之後才開始載入 script3.js。雖然此方法可行,但如果要下載和執行的檔案很多,還是有些麻煩。如果多個檔案的次序十分重要,更好的辦法是將這些檔案按照正確的次序連線成一個檔案。獨立檔案可以一次性下載所有程式碼(由於這是非同步進行的,使用一個大檔案並沒有什麼損失)。
動態指令碼載入是非阻塞 JavaScript 下載中最常用的模式,因為它可以跨瀏覽器,而且簡單易用。
相關推薦
JavaScript的效能優化:載入和執行
隨著Web2.0技術的不斷推廣,越來越多的應用使用 JavaScript 技術在客戶端進行處理,從而使JavaScript在瀏覽器中的效能成為開發者所面臨的最重要的可用性問題。而這個問題又因JavaScript的阻塞特性變的複雜,也就是說當瀏覽器在執行JavaScript
前端效能優化:細說JavaScript的載入與執行
本文主要是從效能優化的角度來探討JavaScript在載入與執行過程中的優化思路與實踐方法,既是細說,文中在涉及原理性的地方,不免會多說幾句,還望各位讀者保持耐心,仔細理解,請相信,您的耐心付出一定會讓您得到與之匹配的回報。緣起隨著使用者體驗的日益重視,前端效能對使用者體驗的
ExtJs效能優化:tab的資料延遲載入
今天碰到一個問題,當點選某一行資料,顯示詳情時,由於詳情又有四個子tab,每個子tab都是一個表格,有各種各樣的請求,當點選該行資料顯示詳情時,所有的資料同時載入,導致頁面卡頓,此時做ExtjS的效能優化是很重要的。通過研究,瞭解了一下ExtJs的效能優化和前端的效
高效能JavaScript之載入和執行
JS在瀏覽器中的效能,可以認為是開發者所面臨的最重要的可行性問題。這個問題因JS的阻塞特性變得複雜,也就是說當瀏覽器在執行JS程式碼時,不能同時做其他任何事情。事實上,大多數瀏覽器都使用單一程序來處理UI(使用者介面)更新和JavaScript指令碼執行,所以同一時刻只能做其中一件事情。JS執行過程耗時越久,
高效能Javascript第一章載入和執行
筆記: 多數瀏覽器使用單一執行緒處理UI重新整理和javascript指令碼執行,同一個時刻只能做一件事 <script>標籤出現,導致頁面的下載和渲染都必須停下來等待指令碼執行完畢。 遇到<scirpt>必須先執行javascript程式碼,再繼續解析
高效能JavaScript(載入和執行)
當瀏覽器遇到 <script> 標籤時,它是沒辦法知道 JavaScript 是否會向DOM中新增內容或引入其他元素,甚至關閉某一個標籤。因此這個時候瀏覽器就會停止處理頁面,先執行JavaScript程式碼,然後再繼續解析和渲染頁面。 改善 將<scrip
Java效能優化:30個小細節,提升Java程式碼執行效率
程式碼優化,一個很重要的課題。可能有些人覺得沒用,一些細小的地方有什麼好修改的,改與不改對於程式碼的執行效率有什麼影響呢?這個問題我是這麼考慮的,就像大海里面的鯨魚一樣,它吃一條小蝦米有用嗎?沒用,但是,吃的小蝦米一多之後,鯨魚就被餵飽了。 程式碼優化也是一樣,如果專案著眼於儘
加快JavaScript載入和執行效率
JavaScript 在瀏覽器中的效能成為開發者所面臨的最重要的可用性問題。而這個問題又因 JavaScript 的阻塞特性變的複雜,也就是說當瀏覽器在執行 JavaScript 程式碼時,不能同時做其他任何事情。本文詳細介紹瞭如何正確的載入和執行 JavaScript
java 效能優化:35 個小細節,讓你提升 java 程式碼的執行效率
前言 程式碼 優化 ,一個很重要的課題。可能有些人覺得沒用,一些細小的地方有什麼好修改的,改與不改對於程式碼的執行效率有什麼影響呢?這個問題我是這麼考慮的,就像大海里面的鯨魚一樣,它吃一條小蝦米有用嗎?沒用,但是,吃的小蝦米一多之後,鯨魚就被餵飽了。 程式碼優化也是一樣,如果專案著眼於儘快無BUG上線,那
程式的載入和執行(三)——《x86組合語言:從真實模式到保護模式》讀書筆記23
程式的載入和執行(三)——讀書筆記23 接著上次的內容說。 關於過程load_relocate_program的講解還沒有完,還差建立棧段描述符和重定位符號表。 1.分配棧空間與建立棧段描述符 462 ;建立程式堆疊段描述符 463
html中引入javascript檔案的載入和執行
以前開發前端頁面,經常發現html檔案中js檔案引入的位置改變有時會引起介面bug,某些效果出問題。這其實是瀏覽器對js檔案載入和執行的機制所致。 瀏覽器順序讀取到html中的script標籤,就從服務端載入js檔案,每載入一個檔案就立刻執行並且不是非同步的——會阻塞後面頁
Android 效能優化:多執行緒
前言 Android Performance Patterns Season 5 主要介紹了 Android 多執行緒環境下的效能問題。通過介紹 Android 提供的多種多執行緒工具類 (AsyncTask, HandlerThread, Inte
乾貨:MySQL效能優化,in和exists
in和exists哪個效能更優 sql指令碼: 上面的sql中 訂單表中(order
JAVA效能優化:35個小細節讓你提升java程式碼的執行效率
程式碼優化,一個很重要的課題。可能有些人覺得沒用,一些細小的地方有什麼好修改的,改與不改對於程式碼的執行效率有什麼影響呢?這個問題
JavaScript運算符:遞增和遞減(++i,--i 和 i++,i-- 的區別)
nbsp key mic comment 包含 -- 效應 1+n com 遞增和遞減操作符直接借鑒自C,而且各有兩個版本:前置型 (遞增 ++i ,遞減 --i )和 後置型 (遞增 i++ ,遞減 i-- )。書本上對兩者的定義是:前置型應該位於要操作的變量之前,而後置
效能優化之載入
效能優化之載入 一、預載入 原理:預載入即提前載入,就是為了讓需要載入的內容在觸發載入之前載入好,觸發載入時只是簡單的展示,這樣會使使用者操作起來更加流暢。但缺點是增加了首次請求的請求數; 使用場景:如選單背景圖切換時提前載入背景圖,減少切換時出現短暫空白現象; 使用方法:預
現代作業系統:程序和執行緒總結
多程序 程序是資源(CPU、記憶體等)分配的基本單位,它是程式執行時的一個例項。程式執行時系統就會建立一個程序,併為它分配資源,然後把該程序放入程序就緒佇列,程序排程器選中它的時候就會為它分配CPU時間,程式開始真正執行。 Linux系統函式fork()可以在父程序中建立一個子程序,
面試問題:程序和執行緒的區別是什麼?
進 程和執行緒的主要差別在於它們是不同的作業系統資源管理方式。程序有獨立的地址空間,一個程序崩潰後,在保護模式下不會對其它程序產生影響,而執行緒只是一個程序中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程序死掉,所以多程序的程
Python效能優化:PyPy、Numba 與 Cython。PyPy的安裝及對應pip的安裝
效能優化討論見參考1:大概意思是,PyPy內建JIT,對純Python專案相容性極好,幾乎可以直接執行並直接獲得效能提升;缺點是對很多C語言庫支援性不好。Numba是一個庫,可以在執行時將Python程式碼編譯為本地機器指令,而不會強制大幅度的改變普通的Python程式碼。Cython是一種Python
【Android】效能優化:電量消耗統計
電量的消耗和使用對於移動裝置非常重要,一項調查問卷顯示,電池的容量和壽命是手機最重要的營銷點:所謂“the one thing that you can't do without”。 硬體 從硬體的角度看,Android電量的消耗主要來自螢幕,CPU,網路裝置和各樣的感測器:指紋,亮度