高性能JS-加載和執(zhí)行

面對(duì)開發(fā)者的瀏覽器的Js性能可以說,最重要的是可用性問題砍艾。這個(gè)問題是復(fù)雜的蒂教,因?yàn)镴s的阻塞特性,換句話說當(dāng)Js代碼正在被執(zhí)行的時(shí)候脆荷,任何其他事情都不會(huì)發(fā)生凝垛。事實(shí)上懊悯,大多數(shù)瀏覽器使用單個(gè)進(jìn)程來處理UI更新和Js執(zhí)行,所以在任何時(shí)刻只有一個(gè)能發(fā)生梦皮。Js占用約多的時(shí)間來執(zhí)行炭分,瀏覽器能夠響應(yīng)用戶操作之前等待的時(shí)間就越長(zhǎng)。

在基礎(chǔ)層面剑肯,意味著每一個(gè)<script>標(biāo)簽都會(huì)使頁面等待腳本被解析和執(zhí)行捧毛,不論Js代碼是內(nèi)聯(lián)的還是外聯(lián)的。頁面下載和渲染必須停止并等待腳本完成執(zhí)行让网,這個(gè)是頁面聲明周期中必要的部分呀忧,因?yàn)槟_本執(zhí)行過程中引起頁面改變。典型的例子是在頁面中使用document.write()溃睹,比如:

<html>
<head>
  <title>Script Example</title>
</head>
<body>
  <p>
  <script type="text/javascript">
    document.write("The date is "+ (new Date()).toDateString());
  </script>
  </p>
</body>
</html>

當(dāng)瀏覽器碰到<script>標(biāo)簽時(shí)而账,沒有辦法知道Js是否會(huì)在<p>標(biāo)簽內(nèi)插入內(nèi)容,因此瀏覽器停止處理頁面丸凭,執(zhí)行Js代碼福扬,然后繼續(xù)解析和渲染頁面。

腳本位置

傳統(tǒng)的惜犀,<script>標(biāo)簽放在<head>標(biāo)簽內(nèi)用于加載Js文件,<link>tags加載外部的css文件狠裹。比如:

<html>
<head>
  <title>Script Example</title>
  <-- Example of inefficient script positioning -->
  <script type="text/javascript" src="file1.js"></script>
  <script type="text/javascript" src="file2.js"></script>
  <script type="text/javascript" src="file3.js"></script>
</head>
<body>
  <p>Hello world!</p>
</body>
</html>  

雖然這段代碼看上去無害的虽界,它實(shí)際上有一個(gè)嚴(yán)重的性能問題:有三個(gè)Js文件在<head>中被加載,因?yàn)槊恳粋€(gè)<script>標(biāo)簽阻塞頁面繼續(xù)渲染涛菠,直到腳本完全地被下載和執(zhí)行莉御。記住,在打開<body>標(biāo)簽之前俗冻,瀏覽器不會(huì)呈現(xiàn)任何頁面內(nèi)容礁叔。把Js放在頁面頭部導(dǎo)致了顯著的延遲,通常用戶還沒有開始閱讀或者與頁面交互之前迄薄,就會(huì)出現(xiàn)空白的頁面琅关。為了懂得如何發(fā)生了,查看下每個(gè)資源下載的瀑布流圖還是很有用的


HX[YHN7%(HAQLWP3NQD]ILL.png

現(xiàn)在瀏覽器都允許并行下載Js文件了讥蔽,這是一個(gè)好消息涣易,因?yàn)?lt;script>標(biāo)簽不必阻塞其他<script>標(biāo)簽下載外部資源了。不幸的是冶伞,依然阻塞其他資源的下載新症,比如圖片。即使下載Js不阻塞其他資源下載响禽,頁面還是必須等待Js代碼下載和執(zhí)行徒爹。問題還是沒有解決荚醒。

因?yàn)槟_本阻塞下載頁面其他資源,所以推薦把所有的<script>標(biāo)簽盡可能放在<body>標(biāo)簽的底部隆嗅,以不影響整個(gè)頁面的下載腌且,比如:

<html>
<head>
  <title>Script Example</title>
  <link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  <p>Hello world!</p>
 
  <-- Example of recommanded script positioning -->
  <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>

合并腳本

因?yàn)槊恳粋€(gè)<script>標(biāo)簽在頁面初始化下載的時(shí)候阻塞頁面渲染,限制頁面上<script>標(biāo)簽的數(shù)量是有幫助的榛瓮。

每一個(gè)Http請(qǐng)求帶來額外的性能開銷铺董,所以下載單個(gè)100KB的文件要比下載4個(gè)25KB的文件來得快。所以禀晓,現(xiàn)在外部Js文件的數(shù)量可以提升頁面的性能精续。

典型的大網(wǎng)站或web應(yīng)用程序會(huì)有多個(gè)Js文件請(qǐng)求。你可以通過合并這些文件為一個(gè)Js文件粹懒,然后使用一個(gè)<script>標(biāo)簽引入重付,來最小化性能影響。

比如凫乖,雅虎創(chuàng)建了用于分發(fā)Yahoo組合的處理程序确垫,如何網(wǎng)站可以通過一個(gè)combo-handled URL拉取任何數(shù)量的YUI文件。比如:

<html>
<head>
  <title>Script Example</title>
  <link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
  <p>Hello world!</p>
  
  <-- Example of recommanded script positioning -->
  <script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js"></script>
</body>
</html>

這個(gè)URL加載了2.7.0版本的yahoo-min.js和event-min.js文件帽芽。這些文件在服務(wù)器上分開存放删掀,但是當(dāng)URL請(qǐng)求的時(shí)候合并在一起返回。使用單個(gè)<script>標(biāo)簽來加載导街,而不是通過兩個(gè)<script>標(biāo)簽披泪。

非阻塞Script

保持Js文件小并且限制http請(qǐng)求數(shù)量只是對(duì)于小型網(wǎng)站的第一步。對(duì)于有這豐富功能的應(yīng)用搬瑰,有更多的Js代碼請(qǐng)求款票,所以保持源代碼小不總是一個(gè)選擇。限制只下載單個(gè)大的Js文件將導(dǎo)致鎖死瀏覽器一個(gè)較長(zhǎng)的時(shí)間泽论,盡管它只有一個(gè)HTTP請(qǐng)求艾少。為了解決這種情況,你需要使用一種非阻塞瀏覽器的方式在頁面中增量的添加更多的Js翼悴。

非阻塞腳本的秘密是在頁面加載完成后加載Js缚够。從技術(shù)層面講,就是在window's load事件觸發(fā)后下載Js代碼抄瓦,有幾個(gè)技術(shù)可以做到潮瓶。

Deferred Script

<script type="text/javascript" src="file1.js" defer></script>

一個(gè)帶有defer的<script>標(biāo)簽可以放置在文檔的任何位置,Js文件會(huì)在<script>標(biāo)簽被解析后開始下載钙姊,但是不會(huì)執(zhí)行毯辅,知道DOM被完全加載(在onload事件之前執(zhí)行)。當(dāng)deferred腳本文件被下載的時(shí)候煞额,它不阻塞瀏覽器思恐,所以頁面上的其他資源能并行下載沾谜。

動(dòng)態(tài)腳本元素

Dom允許你使用js動(dòng)態(tài)創(chuàng)建HTML文檔的幾乎任何部分,當(dāng)然包括<script>標(biāo)簽胀莹。一個(gè)新的<script>標(biāo)簽可以使用標(biāo)準(zhǔn)的DOM方法來簡(jiǎn)單的創(chuàng)建:

var script = document.createElement("script");
script.type="text/javascript";
script.src="file1.js";
document.getElementsByTagName("head")[0].appendChild(script);

這個(gè)新的<script>元素加載源文件file1.js基跑,一旦元素被添加到頁面,文件就開始下載描焰。關(guān)于這個(gè)技術(shù)最重要的是文件下載和執(zhí)行不會(huì)阻塞頁面其他進(jìn)程媳否,不管下載在哪兒初始化。你甚至能夠放置你的代碼在文檔<head>中荆秦,也不會(huì)影響頁面其他部分篱竭。

當(dāng)使用動(dòng)態(tài)腳本節(jié)點(diǎn)加載的文件下載完成,這代碼就會(huì)立刻執(zhí)行步绸,當(dāng)腳本是獨(dú)立有效的掺逼,這會(huì)工作的很好。但可能腳本的執(zhí)行依賴于頁面上其他腳本瓤介,在這種情況下吕喘,你需要跟蹤代碼被完全下載并準(zhǔn)備使用的狀態(tài),這是通過事件觸發(fā)來完成的(腳本加載完成會(huì)觸發(fā)load事件)刑桑。

var script=document.createElement("script");
script.type="text/javascript";

script.onload=function(){
  alert("Script loaded!");
};

script.src="file1.js";
document.getElementsByTagName("head")[0].appendChild(script);

通常氯质,你會(huì)考慮使用簡(jiǎn)單的方法去動(dòng)態(tài)加載Js文件,下面是一個(gè)方法封裝:

function loadScript(url,callback){
  var script =document.createElement("script");
  script.type="text/javascript";
  
  if(script.readyState){
    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);
}

你可能動(dòng)態(tài)加載很多JS文件漾月,但是你得考慮文件加載的順序病梢。主流的瀏覽器為確保腳本執(zhí)行的順序會(huì)按照你指定的來,其他瀏覽器下載和執(zhí)行腳本的順序由他們被服務(wù)器返回的先后順序決定梁肿。你可以通過鏈?zhǔn)较螺d來控制順序,比如:

loadScript("file1.js",function(){
  loadScript("file2.js",function(){
    loadScript("file3.js",function(){
      alert("All files are loaded!");
    })
  })
})

XMLHttpRequest 腳本注入

另一個(gè)非阻塞檢索腳本的方法是使用XMLHttpRequest對(duì)象觅彰,然后注入腳本到頁面吩蔑。

var xhr = new XMLHttpRequest();
xhr.open("get","file1.js",true);
xhr.onreadystatechange=function(){
  if(xhr.readyState==4){
    if(xhr.status >=200 && xhr.status<300 || xhr.status==304){
      var script =document.createElement("script");
      script.type ="text/javascript";
      script.text=xhr.responseText;
      document.body.appendChild(script);
    }
  }
};
xhr.send(null);

這個(gè)方法主要的優(yōu)勢(shì)是你可以下載腳本代碼,但不需要立刻執(zhí)行它填抬。因?yàn)榇a不是通過<script>標(biāo)簽下載的烛芬,它不會(huì)一旦下載就立刻執(zhí)行,允許你延遲它的執(zhí)行飒责,直到你已經(jīng)準(zhǔn)備好讓它執(zhí)行赘娄。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宏蛉,隨后出現(xiàn)的幾起案子遣臼,更是在濱河造成了極大的恐慌,老刑警劉巖拾并,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揍堰,死亡現(xiàn)場(chǎng)離奇詭異鹏浅,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)屏歹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門隐砸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝙眶,你說我怎么就攤上這事季希。” “怎么了幽纷?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵式塌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我霹崎,道長(zhǎng)珊搀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任尾菇,我火速辦了婚禮境析,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘派诬。我一直安慰自己劳淆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布默赂。 她就那樣靜靜地躺著沛鸵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缆八。 梳的紋絲不亂的頭發(fā)上曲掰,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音奈辰,去河邊找鬼栏妖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛奖恰,可吹牛的內(nèi)容都是我干的吊趾。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼瑟啃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼论泛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛹屿,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤屁奏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蜡峰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體了袁,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朗恳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了载绿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粥诫。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖崭庸,靈堂內(nèi)的尸體忽然破棺而出怀浆,到底是詐尸還是另有隱情,我是刑警寧澤怕享,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布执赡,位于F島的核電站,受9級(jí)特大地震影響函筋,放射性物質(zhì)發(fā)生泄漏沙合。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一跌帐、第九天 我趴在偏房一處隱蔽的房頂上張望首懈。 院中可真熱鬧,春花似錦谨敛、人聲如沸究履。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽最仑。三九已至,卻和暖如春炊甲,著一層夾襖步出監(jiān)牢的瞬間泥彤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工卿啡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留全景,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓牵囤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親滞伟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子揭鳞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容