1. 程式人生 > >並行載入與順序執行–《高效能javascript》讀書筆記(jquery載入順序)

並行載入與順序執行–《高效能javascript》讀書筆記(jquery載入順序)

並行載入與順序執行——《高效能javascript》讀書筆記

Javascript檔案(下面簡稱指令碼檔案)需要被HTML檔案引用才能在瀏覽器中執行(廢話嘛(-.-))。在HTML檔案中可以通過不同的方式來引用指令碼檔案,我們需要關注的是,這些方式的具體實現和這些方式可能會帶來的效能問題

首先,引用指令碼必須用到<script>標籤,我們需要了解<script>標籤的特性,引述書中作者原話:

當瀏覽器遇到(內嵌)<script>標籤時,當前瀏覽器無從獲知Javascript是否會修改頁面內容。因此,這時瀏覽器會停止處理頁面,先執行Javascript程式碼,然後再繼續解析和渲染頁面。同樣的情況也發生在使用 src 屬性加在Javascript的過程中(即外鏈 Javascript ),瀏覽器必須先花時間下載外鏈檔案中的程式碼,然後解析並執行它。在這個過程中,頁面渲染和使用者互動完全被阻塞了。

通過上述描述,我們能瞭解到:

每當瀏覽器解析到<script>標籤(無論內嵌還是外鏈)時,瀏覽器會(一根筋地)優先下載、解析並執行該標籤中的Javascript程式碼,而阻塞了其後所有頁面內容的下載和渲染。

下面列舉四種引用指令碼的方式:

慣例的做法

最傳統的方式是在head標籤內插入<script>標籤:

1
2
3
4
5
6
7
8
9
10
<html>
<head>
   <title>Example</title>
   <script type
='text/javascript' src='file1.js'></script>

   <link rel="stylesheet" type="text/css" href="styles/test.css" />
</head>
<body>
   <p>Vtmer Rush~~</p>
</body>
</html>

然而這種常規的做法卻隱藏著嚴重的效能問題。根據上述對<script>標籤特性的描述,我們知道,在該示例中,當瀏覽器解析到<script>標籤(第4行)時,瀏覽器會停止解析其後的內容,而優先下載指令碼檔案,並執行其中的程式碼,這意味著,其後的test.css樣式檔案和<body>標籤都無法被載入,由於<body>標籤無法被載入,那麼頁面自然就無法渲染了。因此在該javascript程式碼完全執行完之前,頁面都是一片空白。

下面貼上一張動態圖來說明上述現象:

上圖有兩點是需要注意的:

  1. 頁面的渲染和javascript程式碼的執行是一起顯示出來的。這說明頁面最開始出現的空白正是由於javascript檔案阻塞特性引起的(為了突出這一現象,我特意外鏈了幾個體積較大的js庫,然後迅雷火力全開…(-.-))。這是因為如果javascript檔案沒有阻塞頁面渲染的話,頁面的渲染一般會先於javascript檔案的載入(一般來說頁面所需要的的css樣式檔案和html檔案的體積會遠遠小於javascript檔案,如果沒有被阻塞,它們會先於javascript檔案下載好,然後立即被瀏覽器解析出來),請對比無阻塞的效果圖點這裡
  2. 圖片的載入是在javascript執行之後才開始的,即javascript阻塞了圖片的載入。

經典的做法

既然<script>標籤會阻塞其後內容的載入,那麼將<script>標籤放到所有頁面內容之後不就可以避免這種糟糕的狀況了嗎?如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<head>
   <title>Example</title>
   <link rel="stylesheet" type="text/css" href="styles/test.css" />
</head>
<body>
   <p>Vtmer Rush~~</p>
   <!-- 推薦的指令碼放置位置 -->
   <script type='text/javascript' src='file1.js'></script>
   <script type='text/javascript' src='file2.js'></script>
   <script type='text/javascript' src='file3.js'></script>
</body>
</html>

將所有的<script>標籤儘可能地放到<body>標籤底部,以儘量避免對頁面其餘部分下載的影響,效果圖如下:

對比上一張動態圖,頁面渲染先於指令碼檔案的執行,說明指令碼檔案不再阻塞頁面渲染了(包括css檔案和img等檔案的下載)

然而作者在後面又介紹了另一種方式——動態載入指令碼。起初我不太明白,把指令碼放到<body>底部就好了,為什麼還需要動態指令碼?多翻了幾回書才發現原來自己忽略了作者的一段話:

(將指令碼放到<body>標籤底部時)儘管指令碼下載會阻塞另一個指令碼,但是頁面的大部分內容已經下載完成並顯示給使用者…

即是說,雖然在IE8+瀏覽器上已經實現了指令碼並行下載,但在某些瀏覽器中(即使指令碼檔案放到了<body>標籤底部),頁面中指令碼仍是一個接著一個載入的。就上述示例程式碼來說,瀏覽器先載入完file1,再去載入file2,最後才輪到file3。雖然此時指令碼已經不影響其他頁面內容了,但我們也同樣希望指令碼之間實現並行下載(即同時開始下載),於是下面給出動態載入指令碼的方法來實現這一想法。

動態指令碼

通過文件物件模型(DOM),我們可以幾乎可以頁面任意地方建立<script>標籤:

1
2
3
4
var script=document.createElement('script');
script.type='text/javascript';
script.src='file1.js';
document.getElementsByTagName('head')[0].appendChild(script);

上述程式碼動態建立了一個外鏈file1的<script>標籤,並將其新增到<head>標籤內。這種技術的重點在於:

無論在何時啟動下載,檔案的下載和執行過程不會阻塞頁面其他程序(包括指令碼載入)。

然而這種方法也是有缺陷的。這種方法載入的指令碼會在下載完成後立即執行,那麼意味著多個指令碼之間的執行順序是無法保證的(除了Firefox和Opera)。當某個指令碼對另一個指令碼有依賴關係時,就很可能發生錯誤了。比如,寫一個jQuery程式碼,需要引入jQuery庫,然而你寫的jQuery程式碼檔案很可能會先完成下載並立即執行,這時瀏覽器會報錯——‘jQuery未定義’之類的,因為此時jQuery庫還未下載完成。於是做出以下改進:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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{//其他瀏覽器
      script.onload=function(){
         callback();
      };
   }
   script.src=url;
   document.getElementsByTagName('head')[0].appendChild(script);
}

上述程式碼改進的地方就是增加了一個回撥函式(4-11行),該函式會在相應指令碼檔案載入完成後被呼叫。這樣便可以實現順序載入了,寫法如下(假設file2依賴file1,file1和file3相互獨立):

1
2
3
4
loadScript('file1.js',function(){
   loadScript('file2.js',function(){});
});
loadScript('file3.js',function(){});

file2會在file1載入完後才開始載入,保證了在file2執行前file1已經準備妥當。而file1和file3是並行下載的,互不影響。

雖然loadScript函式已經足夠好,但還是有些不盡人意的地方——通過分析這段程式碼,我們知道,loadScript函式中的順序載入是以指令碼的阻塞載入來實現的(正如上述紅字部分指出的那樣)。而我們真正想實現的是——指令碼同步下載並按相應順序執行,即並行載入並順序執行。

LABjs庫

LABjs庫能幫我們真正地實現“並行載入與順序呢執行”,推薦寫法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
   <title>Example</title>
   <link rel="stylesheet" type="text/css" href="styles/test.css" />
</head>
<body>
   <p>Vtmer Rush~~</p>
   <!-- 推薦的指令碼放置位置 -->
   <script type='text/javascript' src='LABjs.js'></script>
   <script type='text/javascript'>
      $LAB.script('file1.js')
         .script('file2.js')
         .script('file3.js');
   </script>
</body>
</html>

上述程式碼會並行載入三個指令碼檔案,更讚的是,它會按指令碼的順序來執行這些指令碼。瀑布圖示例如下:

可從圖中看出,jQuery程式碼檔案(text.js)與jQuery庫檔案並行載入,而且先於庫檔案完成下載,此時頁面能正常執行,說明頁面是按順序執行指令碼檔案的。

注:

  • 以上是一些讀書筆記及心得,整理至《高效能javascript》,希望能對大家有所幫助
  • 適遠師兄和hbc提到的seajs和require.js貌似也可以實現,有空去試試

相關推薦

並行載入順序執行–《高效能javascript讀書筆記jquery載入順序

並行載入與順序執行——《高效能javascript》讀書筆記 Javascript檔案(下面簡稱指令碼檔案)需要被HTML檔案引用才能在瀏覽器中執行(廢話嘛(-.-))。在HTML檔案中可以通過不同的方式來引用指令碼檔案,我們需要關注的是,這些方式的具體實現和這些

高效能 JavaScript 讀書筆記

迴圈 1.減少迭代的工作量 2.倒序迴圈 條件語句 if-else && switch 大多數情況下,switch 比 if-else 執行的快。但只有條件數量很大的時候才明顯。 優化 if-else 1.把最可能出現的條件放在最前面。 2.巢

高效能 JavaScript 讀書筆記

字串和正則表示式 字串連線 連線字串有很多方式,常見的有: 1.str = "a" + "b" + "c"; 2.str = "a"; str += "b"; str += "c"; 3.str = ["a","b","c"].join(""); 4.str = "

高效能 JavaScript 讀書筆記

重繪與重排 概念 重排:當元素的寬和高發生改變的時候,就會重新排列元素,稱為重拍。 重繪:將重排的的元素重新繪製在瀏覽器中,稱為重繪。 在元素中新增段落、新增邊框、新增或者刪除元素、修改內外邊距等,一切改變現有元素的位置的操作都會導致重排的發生。相應的,新增背景色這一類沒有改變元素位

高效能 JavaScript 讀書筆記

資料存取 字面量、本地變數、陣列元素、物件成員 字面量只代表自身,不儲存在特定的位置。JavaScript 中的字面量有:字串、數字、布林值、物件、陣列、函式、正則表示式以及特殊的 null 和 undefined 值。 一般來說,訪問本地變數和字面量的事件較少,訪問陣列元素和物件成員花

資料結構演算法JavaScript描述讀書筆記js實現樹

js定義二叉查詢樹 //建立建構函式建立節點 function Node(data){ this.data = data; this.left = null; this.right = null; } function tree(){ this.root = nu

資料結構演算法JavaScript描述讀書筆記高階排序演算法

希爾排序 在插入排序的基礎上,只不過比較的步長不一樣,插入排序比較步長一直是1(即一個一個的比較)。希爾排序的步長第一次一般設定為gap=Math.floor(arr.length/2),之後依次將步長設定為gap/2,直到步長變為1,這個時候徹底轉化成插入排 測試時間普通排序演算法1

資料結構演算法JavaScript描述讀書筆記基本排序演算法

前提準備 //自動生成陣列的函式,n:整數個數,數字在l-r之間 function setData(n,l,r){ var dataStore = []; for(var i=0;i<n;i++){ dataStore[i] = Math.floor(

高效能javascript讀書筆記之操作DOM

DOM:文件物件模型,是一個獨立於語言的,用來操作XML和HTML文件的程式介面(API)。 瀏覽器通常會把DOM和javascript獨立實現,每次連線DOM和ECMAScript,都會被收取“過橋費”(效能消耗)。 優化方法: 1.減少訪問DOM的次數,把運算儘量留在

高效能javascript讀書筆記之注意點

1.避免雙重求值      允許傳人另一段javascript程式碼字串並執行的四種方法:      eval()、Function()建構函式、setTimeout()和setInterval()      當在javascript程式碼中執行另一段javascript程

編寫高效能JavaScript (讀書筆記)

轉載自http://kb.cnblogs.com/page/501177/  作者: Addy Osmani 讀書筆記: 作者以V8為例講解: 一.核心構成部分 二.垃圾回收GC:Garbage Collecation 更喜歡叫做資源回收。 Q1:在JavaScript

android BSP硬件相關子系統讀書筆記1android BSP移植綜述

tin 不用 googl csdn splay 編寫 app開發 意圖過濾器 trac 從linux驅動轉行至Android驅動開發大半年了,一開始就產生了一個很糾結目標和問題,就是不停的google如何porting android!這個問題得到的結果對於初出茅廬的我,感

高效能JavaScript學習筆記5--程式設計實踐

1、eval()效率很低,因為每次呼叫都要建立一個新的直譯器/編譯器例項。2、setTimeout()和setInterval()建議傳入函式而不是字串來作為第一引數。 例如:      setTime

高效能mysql讀書筆記

1 mysql的架構與歷史: 1.1 mysql邏輯架構 mysql將查詢等處理與資料儲存相分離的,可以更加靈活的選取資料儲存的方式。mysql處理架構:連線處理->解析器->查詢快取->優化器->儲存引擎。 第一層:連線處理:包括主要的授權登入、安

高效能MySQL.讀書筆記高可用性

什麼是高可用性 每個應用對可用性的需求各不相同。在設定一個可用時間的目標之前,先問問自己,是不是確實需要達到這個目標。可用性每提高一點,所花費的成本都會遠超之前;可用性的效果和開銷的比例並不是線性的。需要保證多少可用時間,取決於能夠承擔多少成本。高可用性實際上是在宕機造成的

高效能MySQL.讀書筆記優化伺服器設定

MySQL有大量可以修改的引數——但不應該隨便去修改。通常只需要把基本的配置項配置正確(大部分情況下只有很少一些引數是真正重要的),應該將更多的時間花在schema的優化、索引,以及查詢設計上。 確保基本的配置是正確的,如果碰到了問題,並且問題是由於伺服器的某部分導致的,而這恰好可以通過某個配置項解決,那麼需

高效能mysql讀書筆記

四,建立高效能的索引 索引也稱為鍵,索引是一種查詢資料有用的資料結構,索引優化是查詢優化中最優效的手段。 索引可以包含一列或者多列,mysql能夠高效的使用索引中的最左字首列,索引在儲存引擎層實現,不同的儲存引擎有不同的索引。B-tree索引作為大多數資料庫的索引資料結構,

高效能MySQL.讀書筆記作業系統和硬體優化

使用Flashcache 雖然有很多因素需要在快閃記憶體、硬碟和RAM之間權衡,在儲存層次結構中,這些裝置沒有被當作一個整體處理。有時可以使用磁碟和記憶體技術的結合,這就是Flashcache。 Flashcache是一個Linux核心模組,使用Linux的裝置對映器(Device Mapper)。它在記憶體

《大型網站系統Java中介軟體》讀書筆記

前言 只有光頭才能變強。 文字已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongF

《大型網站系統Java中介軟體》讀書筆記

前言 只有光頭才能變強。 文字已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongF