亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JavaScript異步加載淺析

 更新時(shí)間:2014年12月28日 11:16:51   投稿:junjie  
這篇文章主要介紹了JavaScript異步加載淺析,本文講解了腳本延遲執(zhí)行、腳本的完全并行化、可編程的腳本加載等內(nèi)容,需要的朋友可以參考下

前言

關(guān)于JavaScript腳本加載的問(wèn)題,相信大家碰到很多。主要在幾個(gè)點(diǎn)——

1> 同步腳本和異步腳本帶來(lái)的文件加載、文件依賴及執(zhí)行順序問(wèn)題
2> 同步腳本和異步腳本帶來(lái)的性能優(yōu)化問(wèn)題


深入理解腳本加載相關(guān)的方方面面問(wèn)題,不僅利于解決實(shí)際問(wèn)題,更加利于對(duì)性能優(yōu)化的把握并執(zhí)行。
 
先看隨便一個(gè)script標(biāo)簽代碼——


復(fù)制代碼 代碼如下:

<script src="js/myApp.js"></script>


如果放在<head>上面,會(huì)阻塞所有頁(yè)面渲染工作,使得用戶在腳本加載完畢并執(zhí)行完畢之前一直處于“白屏死機(jī)”狀態(tài)。而<body>末尾的打腳本只會(huì)讓用戶看到毫無(wú)生命力的靜態(tài)頁(yè)面,原本應(yīng)該進(jìn)行客戶端渲染的地方卻散布著不起作用的控件和空空如也的方框。拿一個(gè)測(cè)試用例——

復(fù)制代碼 代碼如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>異步加載script</title>
    <script src="js/test.js"></script>
</head>
<body>
    <div>我是內(nèi)容</div>
    <img src="img/test.jpg">
</body>
</html>

其中,test.js中的內(nèi)容——


復(fù)制代碼 代碼如下:

alert('我是head里面的腳本代碼,執(zhí)行這里的js之后,才開(kāi)始進(jìn)行body的內(nèi)容渲染!');


我們會(huì)看到,alert是一個(gè)暫停點(diǎn),此時(shí),頁(yè)面是空白的。但是要注意,此時(shí)整個(gè)頁(yè)面已經(jīng)加載完畢,如果body中包含某些src屬性的標(biāo)簽(如上面的img標(biāo)簽),此時(shí)瀏覽器已經(jīng)開(kāi)始加載相關(guān)內(nèi)容了。總之要注意——js引擎和渲染引擎的工作時(shí)機(jī)是互斥的(一些書(shū)上叫它為UI線程)。

因此,我們需要——那些負(fù)責(zé)讓頁(yè)面更好看、更好用的腳本應(yīng)該立即加載,而那些可以待會(huì)兒再加載的腳本稍后再加載。

一、腳本延遲執(zhí)行

現(xiàn)在越來(lái)越流行把腳本放在頁(yè)面<body>標(biāo)簽的尾部。這樣,一方面用戶可以更快地看到頁(yè)面,另一方面腳本可以直接操作已經(jīng)加載完成的dom元素。對(duì)于大多數(shù)腳本而言,這次“搬家”是個(gè)巨大的進(jìn)步。該頁(yè)面模型如下——


復(fù)制代碼 代碼如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <!--metadata and scriptsheets go here-->
    <script src="headScript.js"></script>
</head>
<body>
    <!--content goes here-->
    <script src="bodyScript.js"></script>
</body>
</html>

這確實(shí)大大加快了頁(yè)面的渲染時(shí)間,但是注意一點(diǎn),這可能讓用戶有機(jī)會(huì)在加載bodyScript之前與頁(yè)面交互。源于瀏覽器在加載完整個(gè)文檔之前無(wú)法加載這些腳本,這對(duì)那些通過(guò)慢速連接傳送的大型文檔來(lái)說(shuō)會(huì)是一大瓶頸。

理想情況下,腳本的加載應(yīng)該與文檔的加載同時(shí)進(jìn)行,并且不影響DOM的渲染。這樣,一旦文檔就緒就可以運(yùn)行腳本,因?yàn)橐呀?jīng)按照<script>標(biāo)簽的次序加載了相應(yīng)腳本。

我們使用defer便能夠完成這樣的需求,即——


復(fù)制代碼 代碼如下:

<script src="deferredScript.js"></script>


添加defer屬性相當(dāng)于告訴瀏覽器:請(qǐng)馬上開(kāi)始加載這個(gè)腳本吧,但是,請(qǐng)等到文檔就緒且此前所有具有defer屬性的腳本都結(jié)束運(yùn)行之后再運(yùn)行它。

這樣,在head標(biāo)簽里放入延遲腳本,技能帶來(lái)腳本置于body標(biāo)簽時(shí)的所有好處,又能讓大文檔的加載速度大幅提升。此時(shí)的頁(yè)面模式便是——


復(fù)制代碼 代碼如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <!--metadata and scriptsheets go here-->
    <script src="headScript.js"></script>
    <script src="deferredScript.js" defer></script>
</head>
<body>
    <!--content goes here-->
</body>
</html>

但是并非所有的瀏覽器都支持defer(對(duì)于一些modern瀏覽器,如果聲明defer,其內(nèi)部腳本將不會(huì)執(zhí)行document.write及DOM渲染操作。IE4+均支持defer屬性)。這意味著,如果想確保自己的延遲腳本能在文檔加載后運(yùn)行,就必須將所有延遲腳本的代碼都封裝在諸如jQuery之$(document).ready之類的結(jié)構(gòu)中。這是值得的,因?yàn)椴畈欢?7%的訪客都能享受到并行加載的好處,同時(shí)另外3%的訪客仍然能使用功能完整的JavaScript。

二、腳本的完全并行化

讓腳本的加載及執(zhí)行再快一步,我不想等到defer腳本一個(gè)接著一個(gè)運(yùn)行(defer讓我們想到一種靜靜等待文檔加載的有序排隊(duì)場(chǎng)景),更不想等到文檔就緒之后才運(yùn)行這些腳本,我想要盡快加載并且盡快運(yùn)行這些腳本。這里也就想到了HTML5的async屬性,但是要注意,它是一種混亂的無(wú)政府狀態(tài)。

例如,我們加載兩個(gè)完全不相干的第三方腳本,頁(yè)面沒(méi)有它們也運(yùn)行得很好,而且也不在乎它們誰(shuí)先運(yùn)行誰(shuí)后運(yùn)行。因此,對(duì)這些第三方腳本使用async屬性,相當(dāng)于一分錢沒(méi)花就提升了它們的運(yùn)行速度。

async屬性是HTML5新增的。作用和defer類似,即允許在下載腳本的同時(shí)進(jìn)行DOM的渲染。但是它將在下載后盡快執(zhí)行(即JS引擎空閑了立馬執(zhí)行),不能保證腳本會(huì)按順序執(zhí)行。它們將在onload 事件之前完成。

Firefox 3.6、Opera 10.5、IE 9 和 最新的Chrome 和 Safari 都支持 async 屬性??梢酝瑫r(shí)使用 async 和 defer,這樣IE 4之后的所有 IE 都支持異步加載,但是要注意,async會(huì)覆蓋掉defer。

那么此時(shí)的頁(yè)面模型如下——

復(fù)制代碼 代碼如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <!--metadata and scriptsheets go here-->
    <script src="headScript.js"></script>
    <script src="deferredScript.js" defer></script>
</head>
<body>
    <!--content goes here-->
    <script src="asyncScript1.js" async defer></script>
    <script src="asyncScript2.js" async defer></script>
</body>
</html>

要注意這里的執(zhí)行順序——各個(gè)腳本文件加載,接著執(zhí)行headScript.js,緊接著在DOM渲染的同時(shí)會(huì)在后臺(tái)加載defferedScript.js。接著在DOM渲染結(jié)束時(shí)將運(yùn)行defferedScript.js和那兩個(gè)異步腳本,要注意對(duì)于支持async屬性的瀏覽器而言,這兩個(gè)腳本將做無(wú)序運(yùn)行。

三、可編程的腳本加載

盡管上面兩個(gè)腳本屬性的功能非常吸引人,但是由于兼容性的問(wèn)題,應(yīng)用并不是很廣泛。故此,我們更多使用腳本加載其他腳本。例如,我們只想給那些滿足一定條件的用戶加載某個(gè)腳本,也就是經(jīng)常提到的“懶加載”。

在瀏覽器API層面,有兩種合理的方法來(lái)抓取并運(yùn)行服務(wù)器腳本——

1> 生成ajax請(qǐng)求并用eval函數(shù)處理響應(yīng)

2> 向DOM插入<script>標(biāo)簽

后一種方式更好,因?yàn)闉g覽器會(huì)替我們操心生成HTTP請(qǐng)求這樣的事。再者,eval也有一些實(shí)際問(wèn)題:泄露作用域,調(diào)試搞得一團(tuán)糟,而且還可能降低性能。因此,想要加載名為feture.js的腳本,我們應(yīng)該使用類似下面的代碼:


復(fù)制代碼 代碼如下:

var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.src = 'feature.js';
head.appendChild(script);

當(dāng)然,我們要處理回調(diào)監(jiān)聽(tīng),HTML5規(guī)范定義了一個(gè)可以綁定回調(diào)的onload屬性。


復(fù)制代碼 代碼如下:

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


不過(guò),IE8及更老的版本并不支持onload,它們支持的是onreadystatechange。而且,對(duì)于錯(cuò)誤處理仍然千奇百怪。在這里,可以多參考一些流行的校本加載庫(kù),如labjs、yepnope、requirejs等。

如下,自己封裝了一個(gè)簡(jiǎn)易loadjs文件——


復(fù)制代碼 代碼如下:

var loadJS = function(url,callback){
    var head = document.getElementsByTagName('head')[0];
    var script = document.createElement('script');
    script.src = url;
    script.type = "text/javascript";
    head.appendChild( script);

    // script 標(biāo)簽,IE下有onreadystatechange事件, w3c標(biāo)準(zhǔn)有onload事件
    // IE9+也支持 W3C標(biāo)準(zhǔn)的onload
    var ua = navigator.userAgent,
        ua_version;
    // IE6/7/8
    if (/MSIE ([^;]+)/.test(ua)) {
        ua_version = parseFloat(RegExp["$1"], 10);
        if (ua_version <= 8) {
            script.onreadystatechange = function(){
                if (this.readyState == "loaded" ){
                    callback();
                }
            }
        } else {
            script.onload = function(){
                callback();
            };
        }
    } else {
        script.onload = function(){
            callback();
        };
    }
};

對(duì)于document.write的方式異步加載腳本,在這里就不說(shuō)了,現(xiàn)在很少有人這么干了,因?yàn)闉g覽器差異性實(shí)在是搞得頭大。

要注意,使用 Image 對(duì)象異步預(yù)加載 js 文件,里面的js代碼將不會(huì)被執(zhí)行。

最后,談一下requirejs中的異步加載腳本。

requirejs不會(huì)保證按順序運(yùn)行目標(biāo)腳本,只是保證它們的運(yùn)行次序能滿足各自的依賴性要求。從而我們確保了盡快的并行加載所有腳本,并有條不紊的按照依賴性拓?fù)浣Y(jié)構(gòu)去執(zhí)行這些腳本。

 

四、總結(jié)

OK,談到這兒,異步加載腳本的陳述也就完了。我再次啰嗦一下這里的優(yōu)化順序——

1> 傳統(tǒng)的方式,我們使用script標(biāo)簽直接嵌入到html文檔中,這里分兩種情況——

  a> 嵌入到head標(biāo)簽中——要注意,這樣做并不會(huì)影響文檔內(nèi)容中其他靜態(tài)資源文件的并行加載,它影響的是,文檔內(nèi)容的渲染,即此時(shí)的DOM渲染就會(huì)被阻塞,呈現(xiàn)白屏。

  b> 嵌入到body標(biāo)簽底部——為了免去白屏現(xiàn)象,我們優(yōu)先進(jìn)行DOM的渲染,再去執(zhí)行腳本,但問(wèn)題又來(lái)了。先說(shuō)第一個(gè)問(wèn)題——如果DOM文檔內(nèi)容比較大,交互事件綁定便有了延遲,體驗(yàn)便差了些。當(dāng)然,我們需要根據(jù)需求而定,讓重要的腳本優(yōu)先執(zhí)行。再說(shuō)第二個(gè)問(wèn)題——由于腳本文件至于body底部,導(dǎo)致對(duì)于這些腳本的加載相對(duì)于至于head中的腳本而言,它們的加載便有了延遲。所以,至于body底部,也并非是優(yōu)化的終點(diǎn)。

  c> 添加defer屬性——我們希望腳本盡早的進(jìn)行并行加載,我們把這批腳本依舊放入head中。腳本的加載應(yīng)該與文檔的加載同時(shí)進(jìn)行,并且不影響DOM的渲染。這樣,一旦文檔就緒就可以運(yùn)行腳本。所以便有了defer這樣屬性。但是要注意它的兼容性,對(duì)于不支持defer屬性的瀏覽器,我們需要將代碼封裝在諸如jQuery之$(document).ready中。需要注意一點(diǎn),所有的defer屬性的腳本,是按照其出場(chǎng)順序依次執(zhí)行,因此,它同樣嚴(yán)格同步。

 2> 上一點(diǎn),講的都是同步執(zhí)行腳本(要注意,這些腳本的加載過(guò)程是并行的,只不過(guò)是誰(shuí)先觸發(fā)請(qǐng)求誰(shuí)后觸發(fā)請(qǐng)求的區(qū)別而已),接下來(lái)的優(yōu)化點(diǎn)便是“并行執(zhí)行腳本”,當(dāng)然,我們知道,一個(gè)時(shí)間點(diǎn),只有執(zhí)行一個(gè)js文件,這里的“并行”是指,誰(shuí)先加載完了,只要此時(shí)js引擎空閑,立馬執(zhí)行之。這里的優(yōu)化分成兩種——

  a> 添加async這個(gè)屬性——確實(shí)能夠完成上面我們所說(shuō)的優(yōu)化點(diǎn),但是它有很高的局限性,即僅僅是針對(duì)非依賴性腳本加載,最恰當(dāng)?shù)睦颖闶且攵鄠€(gè)第三方腳本了。還有就是與deffer屬性的合用,實(shí)在是讓人大費(fèi)腦筋。當(dāng)然,它也存在兼容性問(wèn)題。以上三個(gè)問(wèn)題便導(dǎo)致其應(yīng)用并不廣泛。當(dāng)使用async的時(shí)候,一定要嚴(yán)格注意依賴性問(wèn)題。

  b> 腳本加載腳本——很顯然,我們使用之來(lái)達(dá)到“并行執(zhí)行腳本”的目的。同時(shí),我們也方便去控制腳本依賴的問(wèn)題,我們便使用了如requirejs中對(duì)于js異步加載的智能化加載管理。

好,寫(xiě)到這兒。

這里,我僅僅談的是異步加載腳本的相關(guān)內(nèi)容。還有一塊兒內(nèi)容,便是異步加載樣式文件或者其他靜態(tài)資源。待續(xù)......

相關(guān)文章

最新評(píng)論