加載和運行JS的正確姿勢

前言

kyrieliuの《高性能JavaScript》讀書筆記。

script標簽是一個很“霸道”的狠角色,它的每次出現(xiàn)都讓頁面等待腳本的解析和執(zhí)行。也就是說,不管當前的javascript代碼是內(nèi)嵌還是包含在外鏈文件中膛壹,頁面的下載和渲染都必須停下來等待腳本執(zhí)行完成。
其實唉堪,script標簽的“霸道”是必須的模聋,因為頁面的生存周期中,腳本的執(zhí)行可能會修改頁面的內(nèi)容唠亚。
總之链方,在解析和執(zhí)行js的這個過程中,頁面渲染和用戶交互完全被阻塞了灶搜。

腳本位置

說起腳本的位置祟蚀,腦海里不禁想起來那兩句真言:

  1. css放在<head>
  2. js放在</body>

至于why呢,舉個栗砸:

<html>
    <head>
        <title>一個栗子</title>
        <script type='text/javascript' src='js_file_1.js'></script>
        <script type='text/javascript' src='js_file_2.js'></script>
        <script type='text/javascript' src='js_file_3.js'></script>
        <link rel='stylesheet' type='text/css' href='css_file.css' />
    </head>
    
    <body>
        <p>hello from kyrieliu.</p>
    </body>
</html>

這一段看似正常的代碼實際上有著肥腸嚴重的性能問題:在head中加載了三個js文件割卖。
前面我有說到前酿,js文件會阻塞頁面渲染,知道它們?nèi)肯螺d并執(zhí)行完畢后鹏溯,頁面渲染才能繼續(xù)(無情無恥無理取鬧)罢维。
縱觀一份html文檔,用戶真正看得見的內(nèi)容基本上都寫在body標簽里丙挽,也就是說肺孵,瀏覽器在解析到body標簽之前,不會渲染頁面的任何部分∪∈裕現(xiàn)在又寫了三個腳本到head標簽里面悬槽,好嘞,這下子渲染的延遲更明顯了瞬浓,用戶在打開這樣的一個頁面時,看到了what蓬坡?白屏猿棉!用戶不能瀏覽頁面的內(nèi)容,更無法與頁面進行交互屑咳。

場景還原

第一個js文件開始下載萨赁,與此同時阻塞了頁面其他文件的下載。等呀等兆龙,ok杖爽,第一個js文件終于下載完了敲董,第二個js按捺不住自己喜悅的心情,正要開始下載慰安,突然腋寨,第一個js文件說:“且慢,老子還沒有完事呢”化焕,第二個js文件嚇了一跳萄窜,站在原地不動。第一個js文件開始執(zhí)行撒桨,等到執(zhí)行完畢查刻,第二個js文件才得以開始下載。
總之凤类,每個文件必須等到前一個文件下載并執(zhí)行完成才會開始下載穗泵。
在這些文件"one by one"的下載執(zhí)行過程中,用戶看到的則是一片空白谜疤。

不是那么好的好消息

IE8/Firefox3.5/Safari 4/Chrome 2都允許并行下載js文件佃延,也就是說,script標簽在下載外部資源時茎截,不會阻塞其他的script標簽苇侵。
遺憾的是:

  1. js文件的下載過程仍然會阻塞其他資源的下載,比如圖片企锌。
  2. 頁面仍然必須等到所有js代碼下載并執(zhí)行完畢才能繼續(xù)渲染榆浓。

因此,盡管瀏覽器通過允許并行下載提高了性能撕攒,但腳本阻塞仍然是一個問題陡鹃。
綜上,推薦將所有的script標簽盡可能的放在body標簽的底部抖坪,以盡量減少對整個頁面渲染的影響萍鲸。

組織腳本

既然每個script標簽初始下載時都會阻塞頁面渲染,那么我們可以通過減少頁面上script標簽的數(shù)量來改善這一情況擦俐。不光是外鏈的腳本脊阴,內(nèi)嵌腳本的數(shù)量同樣也要限制(畢竟執(zhí)行js代碼也會阻塞頁面的渲染)。
多于外鏈的腳本蚯瞧,這里的情況有一點需要額外注意的地方:考慮HTTP請求會帶來額外的性能開銷嘿期,所以下載單個100kb的文件要比下載四個25kb的文件更快。從這個角度出發(fā)埋合,更能說明減少外鏈腳本文件的數(shù)量將會改善性能备徐。
What u should do?合并腳本甚颂!

無阻塞的腳本

隨著web應用的功能越豐富蜜猾,所需要的js代碼就越多秀菱,所以精簡源代碼也并不總是可行。盡管下載單個較大的js文件只產(chǎn)生一次HTTP請求蹭睡,但這樣做卻會鎖死瀏覽器一大段時間衍菱。
為了避免這種情況,需要向頁面中逐步加載js文件棠笑,這樣做梦碗,在某種程度上來說不會阻塞瀏覽器。

延遲的腳本

HTML4為script標簽定義了一個擴展屬性:defer蓖救。Defer表明本元素所含的腳本不會修改DOM洪规,因此代碼能安全的延遲執(zhí)行
帶有defer屬性的script標簽可以放在文檔的任何位置(不會阻塞瀏覽器的其他進程循捺,此類文件可以與頁面中的其他資源并行下載)斩例,對應的js代碼將在頁面解析到script標簽時開始下載,但并不會執(zhí)行从橘,(onload事件被出發(fā)前)才會執(zhí)行念赶。
PS:截至這本書的第一版(2010年11月),這個屬性對IE和Firefox的支持性比較好(我的天居然有IE)恰力,如果真要投入到實際的項目中叉谜,不妨先去檢查一下瀏覽器的兼容性先~

動態(tài)腳本元素

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'js_file.js';
document.getElementsByTagName('body')[0].appendChild(script);

Firefox/Opera/Chrome和Safari 3+會在script元素接收完成時出發(fā)一個load事件,所以你可以通過監(jiān)聽這個事件來獲得腳本加載完成時的狀態(tài):

script.onload = function(){
    console.log('script loaded.');
}

一向特立獨行的IE自然有他的另一套:觸發(fā)一個readystatechange事件踩萎。script元素提供一個readyState屬性停局,有以下五種取值:

  1. "uninitialized"
  2. "loading"
  3. "loaded"
  4. "interactive" 數(shù)據(jù)完成下載但尚不可用
  5. "complete"

所以,

script.onreadystatechange = function(){
    if (script.readyState == 'loaded' || script.readyState == 'complete'){
        script.onreadystatechange = null;
        console.log('script loaded.');
    }
}

至此香府,我們得到了一個可以應用于廣泛瀏覽器的動態(tài)加載腳本用的函數(shù):

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();
        }
    }
}

可以這么用:

loadScript('file.js',function(){
    console.log('script loaded.');
});

也可以這么用:

loadScript('file_1.js',function(){
    loadScript('file_2.js',function(){
        loadScript('file_3.js',function(){
            console.log('all files are loaded.');
        });
    });
});

XMLHttpRequest腳本注入

標題看起來很高大上的樣子董栽,其實就是Ajax。

var xhr = new XMLHttpRequest();
xhr.open('get','file.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);

局限:跨域問題企孩。

小結(jié)

  • body閉合標簽之前锭碳,將所有的script標簽放到頁面底部。這樣能確保在腳本執(zhí)行前頁面已經(jīng)完成了渲染勿璃。
  • 合并腳本擒抛。頁面中的script標簽越少,加載也就越快补疑,響應也更迅速闻葵。
  • 無阻塞下載js:
    1. script標簽的defer屬性
    2. 動態(tài)創(chuàng)建script元素來下載并執(zhí)行代碼
    3. 使用XHR對象下載js代碼并注入頁面中。
圖片發(fā)自簡書App
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末癣丧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子栈妆,更是在濱河造成了極大的恐慌胁编,老刑警劉巖厢钧,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嬉橙,居然都是意外死亡早直,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門市框,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霞扬,“玉大人,你說我怎么就攤上這事枫振∮髌裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵粪滤,是天一觀的道長斧拍。 經(jīng)常有香客問我,道長杖小,這世上最難降的妖魔是什么肆汹? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮予权,結(jié)果婚禮上昂勉,老公的妹妹穿的比我還像新娘。我一直安慰自己扫腺,他們只是感情好岗照,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斧账,像睡著了一般谴返。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咧织,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天嗓袱,我揣著相機與錄音,去河邊找鬼习绢。 笑死渠抹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的闪萄。 我是一名探鬼主播梧却,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼败去!你這毒婦竟也來了放航?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤圆裕,失蹤者是張志新(化名)和其女友劉穎广鳍,沒想到半個月后荆几,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡赊时,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年吨铸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祖秒。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡诞吱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竭缝,到底是詐尸還是另有隱情房维,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布歌馍,位于F島的核電站握巢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏松却。R本人自食惡果不足惜暴浦,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晓锻。 院中可真熱鬧歌焦,春花似錦、人聲如沸砚哆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躁锁。三九已至纷铣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間战转,已是汗流浹背搜立。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留槐秧,地道東北人啄踊。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像刁标,于是被迫代替她去往敵國和親颠通。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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

  • JavaScript腳本對現(xiàn)代網(wǎng)站來說是必不可少的。當用戶訪問站點,需要下載各種資源陈瘦,例如JS腳本蝇恶,CSS撵儿,圖片乘客,...
    張歆琳閱讀 9,060評論 0 24
  • 面對開發(fā)者的瀏覽器的Js性能可以說,最重要的是可用性問題淀歇。這個問題是復雜的,因為Js的阻塞特性匈织,換句話說當Js代碼...
    Addy_Zhou閱讀 809評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,101評論 25 707
  • JavaScript的加載和執(zhí)行(Loading and Execution) JavaScript的阻塞特性 當...
    梁同學de自言自語閱讀 639評論 3 2
  • 這是家鄉(xiāng)的海灣浪默,徜徉其中,享受和風輕拂缀匕,我走在海上棧道纳决,看到一幅幅美麗動人的圖畫,我被深深吸引著乡小,我被深...
    梁汝閱讀 317評論 2 1