多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國(guó)內(nèi)最全I(xiàn)T社區(qū)平臺(tái) 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁(yè) > web前端 > 網(wǎng)絡(luò)優(yōu)化 > 讓我們?cè)倭牧臑g覽器資源加載優(yōu)化

讓我們?cè)倭牧臑g覽器資源加載優(yōu)化

來(lái)源:程序員人生   發(fā)布時(shí)間:2014-10-11 08:00:01 閱讀次數(shù):6996次

  幾乎每一個(gè)前端程序員都知道應(yīng)該把script標(biāo)簽放在頁(yè)面底部。關(guān)于這個(gè)經(jīng)典的論述可以追溯到Nicholas的 High Performance Javasript 這本書的第一章Loading and Execution中,他之所以建議這么做是因?yàn)椋?/p>

Put all <script> tags at the bottom of the page, just inside of the closing </body> tag. This ensures that the page can be almost completely rendered before script execution begins.

  簡(jiǎn)而言之,如果瀏覽器加載并執(zhí)行腳本,會(huì)引起頁(yè)面的渲染被暫停,甚至還會(huì)阻塞其他資源(比如圖片)的加載。為了更快的給用戶呈現(xiàn)網(wǎng)頁(yè)內(nèi)容,更好的用戶體驗(yàn),應(yīng)該把腳本放在頁(yè)面底部,使之最后加載。

  為什么要在標(biāo)題中使用“再”這個(gè)字?因?yàn)樵诠ぷ髦兄饾u發(fā)現(xiàn),我們經(jīng)常談?wù)摰囊恍╉?yè)面優(yōu)化技巧,比如上面所說(shuō)的總是把腳本放在頁(yè)面的底部,壓縮合并樣式或者腳本文件等,時(shí)至今日已不再是最佳的解決方案,甚至事與愿違,轉(zhuǎn)化為性能的毒藥。這篇文章所要聊的,便是展示某些不被人關(guān)注的瀏覽器特性或者技巧,來(lái)繼續(xù)完成資源加載性能優(yōu)化的任務(wù)。

  一. Preloader

  什么是Preloader

  首先讓我們看一看這樣一類資源分布的頁(yè)面:

<head>
    <link rel="stylesheet" type="text/css" href="">
    <script type="text/javascript"></script>
</head>
<body>
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <img src="">
    <script type="text/javascript"></script>
    <script type="text/javascript"></script>
    <script type="text/javascript"></script>
</body>

  這類頁(yè)面的特點(diǎn)是,一個(gè)外鏈腳本置于頁(yè)面頭部,三個(gè)外鏈腳本置于頁(yè)面的底部,并且是故意跟隨在一系列img之后,在Chrome中頁(yè)面加載的網(wǎng)絡(luò)請(qǐng)求瀑布圖如下:

  值得注意的是,雖然腳本放置在圖片之后,但加載仍先于圖片。為什么會(huì)出現(xiàn)這樣的情況?為什么故意置后資源能夠提前得到加載?

  雖然瀏覽器引擎的實(shí)現(xiàn)不同,但原理都十分的近似。不同瀏覽器的制造廠商們(vendor)非常清楚瀏覽器的瓶頸在哪(比如network, javascript evaluate, reflow, repaint)。針對(duì)這些問(wèn)題,瀏覽器也在不斷的進(jìn)化,所以我們才能看到更快的腳本引擎,調(diào)用GPU的渲染等一推陳出新的優(yōu)化技術(shù)和方案。

  同樣在資源加載上,早在IE8開始,一種叫做lookahead pre-parser(在Chrome中稱為preloader)的機(jī)制就已經(jīng)開始在不同瀏覽器中興起。IE8相對(duì)于之前IE版本的提升除了將每臺(tái)host最高并行下載的資源數(shù)從2提升至6,并且能夠允許并行下載腳本文件之外,最后就是這個(gè)lookahead pre-parser機(jī)制

  但我還是沒有詳述這是一個(gè)什么樣的機(jī)制,不著急,首先看看與IE7的對(duì)比:

  以上面的頁(yè)面為例,我們看看IE7下的瀑布圖:

  底部的腳本并沒有提前被加載,并且因?yàn)橛捎趩蝹€(gè)域名最高并行下載數(shù)2的限制,資源總是兩個(gè)兩個(gè)很整齊的錯(cuò)開并行下載。

  但在IE8下,很明顯底部腳本又被提前:

  并沒有統(tǒng)一的標(biāo)準(zhǔn)規(guī)定這套機(jī)制應(yīng)具備何種功能已經(jīng)如何實(shí)現(xiàn)。但你可以大致這么理解:瀏覽器通常會(huì)準(zhǔn)備兩個(gè)頁(yè)面解析器parser,一個(gè)(main parser)用于正常的頁(yè)面解析,而另一個(gè)(preloader)則試圖去文檔中搜尋更多需要加載的資源,但這里的資源通常僅限于外鏈的js、stylesheet、image;不包括audio、video等。并且動(dòng)態(tài)插入頁(yè)面的資源無(wú)效。

  但細(xì)節(jié)方面卻值得注意:

  1. 比如關(guān)于preloader的觸發(fā)時(shí)機(jī),并非與解析頁(yè)面同時(shí)開始,而通常是在加載某個(gè)head中的外鏈腳本阻塞了main parser的情況下才啟動(dòng);

  2. 也不是所有瀏覽器的preloader會(huì)把圖片列為預(yù)加載的資源,可能它認(rèn)為圖片加載過(guò)于耗費(fèi)帶寬而不把它列為預(yù)加載資源之列;

  3. preloader也并非最優(yōu),在某些瀏覽器中它會(huì)阻塞body的解析。因?yàn)橛械臑g覽器將頁(yè)面文檔拆分為head和body兩部分進(jìn)行解析,在head沒有解析完之前,body不會(huì)被解析。一旦在解析head的過(guò)程中觸發(fā)了preloader,這無(wú)疑會(huì)導(dǎo)致head的解析時(shí)間過(guò)長(zhǎng)。

  Preloader在響應(yīng)式設(shè)計(jì)中的問(wèn)題

  preloader的誕生本是出于一番好意,但好心也有可能辦壞事。

  filamentgroup有一種著名的響應(yīng)式設(shè)計(jì)的圖片解決方案Responsive Design Images:

<html>
<head>
    <title></title>
    <script type="text/javascript" src="./responsive-images.js"></script>
</head>
<body>
    <img src="./running.jpg?medium=_imgs/running.medium.jpg&large=_imgs/running.large.jpg">
</body>
</html>

  它的工作原理是,當(dāng)responsive-images.js加載完成時(shí),它會(huì)檢測(cè)當(dāng)前顯示器的尺寸,并且設(shè)置一個(gè)cookie來(lái)標(biāo)記當(dāng)前尺寸。同時(shí)你需要在服務(wù)器端準(zhǔn)備一個(gè).htaccess文件,接下來(lái)當(dāng)你請(qǐng)求圖片時(shí),.htaccess中的配置會(huì)檢測(cè)隨圖片請(qǐng)求異同發(fā)送的Cookie是被設(shè)置成medium還是large,這樣也就保證根據(jù)顯示器的尺寸來(lái)加載對(duì)于的圖片大小。

  很明顯這個(gè)方案成功的前提是,js執(zhí)行先于發(fā)出圖片請(qǐng)求。但在Chrome下打開,你會(huì)發(fā)現(xiàn)執(zhí)行順序是這樣:

  responsive-images.js和圖片幾乎是同一時(shí)間發(fā)出的請(qǐng)求。結(jié)果是第一次打開頁(yè)面給出的是默認(rèn)小圖,如果你再次刷新頁(yè)面,因?yàn)镃ookie才設(shè)置成功,服務(wù)器返回的是大圖。

  嚴(yán)格意義上來(lái)說(shuō)在某些瀏覽器中這不一定是preloader引起的問(wèn)題,但preloader引起的問(wèn)題類似:插入腳本的順序和位置或許是開發(fā)者有意而為之的,但preloader的這種“聰明”卻可能違背開發(fā)者的意圖,造成偏差。

  如果你覺得上一個(gè)例子還不夠說(shuō)明問(wèn)題的話,最后請(qǐng)考慮使用picture(或者@srcset)元素的情況:

<picture>
    <source src="med.jpg" media="(min-width: 40em)" />
    <source src="sm.jpg"/>
    <img src="fallback.jpg" alt="" />
</picture>

  在preloader搜尋到該元素并且試圖去下載該資源時(shí),它應(yīng)該怎么辦?一個(gè)正常的paser應(yīng)該是在解析該元素時(shí)根據(jù)當(dāng)時(shí)頁(yè)面的渲染布局去下載,而當(dāng)時(shí)這類工作不一定已經(jīng)完成,preloader只是提前找到了該元素。退一步來(lái)說(shuō),即使不考慮頁(yè)面渲染的情況,假設(shè)preloader在這種情形下會(huì)觸發(fā)一種默認(rèn)加載策略,那應(yīng)該是"mobile first"還是"desktop first"?默認(rèn)應(yīng)該加載高清還是低清照片?

  二. JS Loader

  理想是豐滿的,現(xiàn)實(shí)是骨感的。出于種種的原因,我們幾乎從不直接在頁(yè)面上插入js腳本,而是使用第三方的加載器,比如seajs或者requirejs。關(guān)于使用加載器和模塊化開發(fā)的優(yōu)勢(shì)在這里不再贅述。但我想回到原點(diǎn),討論應(yīng)該如何利用加載器,就從seajs與requirejs的不同聊起。

  在開始之前我已經(jīng)假設(shè)你對(duì)requirejs與seajs語(yǔ)法已經(jīng)基本熟悉了,如果還沒有,請(qǐng)移步這里:

  • CMD標(biāo)準(zhǔn):https://github.com/cmdjs/specification/blob/master/draft/module.md
  • AMD標(biāo)準(zhǔn):https://github.com/amdjs/amdjs-api/blob/master/AMD.md

  BTW: 如果你還是習(xí)慣在部署上線前把所有js文件合并打包成一個(gè)文件,那么seajs和requirejs其實(shí)對(duì)你來(lái)說(shuō)并無(wú)區(qū)別。

  seajs與requirejs在模塊的加載方面是沒有差異的,無(wú)論是requirejs在定義模塊時(shí)定義的依賴模塊,還是seajs在factory函數(shù)中require的依賴模塊,在會(huì)在加載當(dāng)前模塊時(shí)被載入,異步,并且順序不可控。差異在于factory函數(shù)執(zhí)行的時(shí)機(jī)。

  執(zhí)行差異

  為了增強(qiáng)對(duì)比,我們?cè)诙x依賴模塊的時(shí)候,故意讓它們的factory函數(shù)要執(zhí)行相當(dāng)長(zhǎng)的時(shí)間,比如1秒:

// dep_A.js定義如下,dep_B、dep_C定義同理

define(function(require, exports, module) {
    (function(second) {
        var start = +new Date();
        while (start + second * 1000 > +new Date()) {}
    })(window.EXE_TIME);
    // window.EXE_TIME = 1;此處會(huì)連續(xù)執(zhí)行1s

    exports.foo = function() {
        console.log("A");
    }
})

  為了增強(qiáng)對(duì)比,設(shè)置了三組進(jìn)行對(duì)照試驗(yàn),分別是:

//require.js:
require(["dep_A", "dep_B", "dep_C"], function(A, B, C) {
});
//sea.js:
define(function(require, exports, module) {
    var mod_A = require("dep_A");
    var mod_B = require("dep_B");
    var mod_C = require("dep_C");
});
//sea.js(定義依賴但并不require):
define(["dep_A", "dep_B", "dep_C"], function(require, exports, module){
}

  接下來(lái)我們看看代碼執(zhí)行的瀑布圖:

  1.require.js:在加載完依賴模塊之后立即執(zhí)行了該模塊的factory函數(shù)

  2.sea.js: 下面兩張圖應(yīng)該放在一起比較。兩處代碼都同時(shí)加載了依賴模塊,但因?yàn)闆]有require的關(guān)系,第三張圖中沒有像第二張圖那樣執(zhí)行耗時(shí)的factory函數(shù)。可見seajs執(zhí)行的原則正如CMD標(biāo)準(zhǔn)中所述Execution must be lazy。

  我想進(jìn)一步表達(dá)的是,無(wú)論requirejs和seajs,通常來(lái)說(shuō)大部分的邏輯代碼都會(huì)放在模塊的factory函數(shù)中,所以factory函數(shù)執(zhí)行的代價(jià)是非常大的。但上圖也同樣告訴我們模塊的define,甚至模塊文件的Evaluate代價(jià)非常小,與factory函數(shù)無(wú)關(guān)。所以我們是不是應(yīng)該盡可能的避免執(zhí)行factory函數(shù),或者等到我們需要的指定功能的時(shí)候才執(zhí)行對(duì)應(yīng)的factory函數(shù)?比如:

document.body.onclick = function () {
    require(some_kind_of_module);
}

  這是非常實(shí)際的問(wèn)題,比如愛奇藝一個(gè)視頻播放的頁(yè)面,我們有沒有必要在第一屏加載頁(yè)面的時(shí)候就加載登陸注冊(cè),或者評(píng)論,或者分享功能呢?因?yàn)橛蟹浅4蟮目赡苡脩糁皇莵?lái)這里看這個(gè)視頻,直至看完視頻它都不會(huì)用到登陸注冊(cè)功能,也不會(huì)去分享這個(gè)視頻等。加載這些功能不僅僅對(duì)瀏覽器是一個(gè)負(fù)擔(dān),還有可能調(diào)用后臺(tái)的接口,這樣的性能消耗是非常可觀的。

  我們可以把這樣稱之為"懶執(zhí)行"。雖然seajs并非有意實(shí)現(xiàn)如上所說(shuō)的“懶執(zhí)行”(它只是在盡可能遵循CommonJS標(biāo)準(zhǔn)靠近)。但“懶執(zhí)行”確實(shí)能夠有助于提升一部分性能。

  但也有人會(huì)對(duì)此產(chǎn)生顧慮。

  記得玉伯轉(zhuǎn)過(guò)的一個(gè)帖子:SeaJS與RequireJS最大的區(qū)別。我們看看其中反對(duì)這么做的人的觀點(diǎn):

我個(gè)人感覺requirejs更科學(xué),所有依賴的模塊要先執(zhí)行好。如果A模塊依賴B。當(dāng)執(zhí)行A中的某個(gè)操doSomething()后,再去依賴執(zhí)行B模塊require('B');如果B模塊出錯(cuò)了,doSomething的操作如何回滾? 很多語(yǔ)言中的import, include, useing都是先將導(dǎo)入的類或者模塊執(zhí)行好。如果被導(dǎo)入的模塊都有問(wèn)題,有錯(cuò)誤,執(zhí)行當(dāng)前模塊有何意義?

而依賴dependencies是工廠的原材料,在工廠進(jìn)行生產(chǎn)的時(shí)候,是先把原材料一次性都在它自己的工廠里加工好,還是把原材料的工廠搬到當(dāng)前的factory來(lái)什么時(shí)候需要,什么時(shí)候加工,哪個(gè)整體時(shí)間效率更高?

  首先回答第一個(gè)問(wèn)題。

  第一個(gè)問(wèn)題的題設(shè)并不完全正確,“依賴”和“執(zhí)行”的概念比較模糊。編程語(yǔ)言執(zhí)行通常分為兩個(gè)階段,編譯(compilation)和運(yùn)行(runtime)。對(duì)于靜態(tài)語(yǔ)言(比如C/C++)來(lái)說(shuō),在編譯時(shí)如果出現(xiàn)錯(cuò)誤,那可能之前的編譯都視為無(wú)效,的確會(huì)出現(xiàn)描述中需要回滾或者重新編譯的問(wèn)題。但對(duì)于動(dòng)態(tài)語(yǔ)言或者腳本語(yǔ)言,大部分執(zhí)行都處在運(yùn)行時(shí)階段或者解釋器中:假設(shè)我使用Nodejs或者Python寫了一段服務(wù)器運(yùn)行腳本,在持續(xù)運(yùn)行了一段時(shí)間之后因?yàn)槟稠?xiàng)需求要加載某個(gè)(依賴)模塊,同時(shí)也因?yàn)檫@個(gè)模塊導(dǎo)致服務(wù)端掛了——我認(rèn)為這時(shí)并不存在回滾的問(wèn)題。在加載依賴模塊之前當(dāng)前的模塊的大部分功能已經(jīng)成功運(yùn)行了。

  再回答第二個(gè)問(wèn)題。

  對(duì)于“工廠”和“原材料”的比喻不夠恰當(dāng)。難道依賴模塊沒有加載完畢當(dāng)前模塊就無(wú)法工作嗎?requirejs的確是這樣的,從上面的截圖可以看出,依賴模塊總是先于當(dāng)前模塊加載和執(zhí)行完畢。但我們考慮一下基于CommonJS標(biāo)準(zhǔn)的Nodejs的語(yǔ)法,使用require函數(shù)加載依賴模塊可以在頁(yè)面的任何位置,可以只是在需要的時(shí)候。也就是說(shuō)當(dāng)前模塊不必在依賴模塊加載完畢后才執(zhí)行。

  你可能會(huì)問(wèn),為什么要拿AMD標(biāo)準(zhǔn)與CommonJS標(biāo)準(zhǔn)比較,而不是CMD標(biāo)準(zhǔn)?

  玉伯在CommonJS 是什么這篇文章中已經(jīng)告訴了我們CMD某種程度上遵循的就是CommonJS標(biāo)準(zhǔn):

從上面可以看出,Sea.js 的初衷是為了讓 CommonJS Modules/1.1 的模塊能運(yùn)行在瀏覽器端,但由于瀏覽器和服務(wù)器的實(shí)質(zhì)差異,實(shí)際上這個(gè)夢(mèng)無(wú)法完全達(dá)成,也沒有必要去達(dá)成。

更好的一種方式是,Sea.js 專注于 Web 瀏覽器端,CommonJS 則專注于服務(wù)器端,但兩者有共通的部分。對(duì)于需要在兩端都可以跑的模塊,可以 有便捷的方案來(lái)快速遷移。

  其實(shí)AMD標(biāo)準(zhǔn)的推出同時(shí)也是遵循CommonJS,在requirejs官方文檔的COMMONJS NOTES中說(shuō)道:

CommonJS defines a module format. Unfortunately, it was defined without giving browsers equal footing to other JavaScript environments. Because of that, there are CommonJS spec proposals for Transport formats and an asynchronous require.

RequireJS tries to keep with the spirit of CommonJS, with using string names to refer to dependencies, and to avoid modules defining global objects, but still allow coding a module format that works well natively in the browser.

  CommonJS當(dāng)然是一個(gè)理想的標(biāo)準(zhǔn),但至少現(xiàn)階段對(duì)瀏覽器來(lái)說(shuō)還不夠友好,所以才會(huì)出現(xiàn)AMD與CMD,其實(shí)他們都是在做同一件事,就是致力于前端代碼更友好的模塊化。所以個(gè)人認(rèn)為依賴模塊的加載和執(zhí)行在不同標(biāo)準(zhǔn)下實(shí)現(xiàn)不同,可以理解為在用不同的方式在完成同一個(gè)目標(biāo), 并不是一件太值得過(guò)于糾結(jié)的事。

  懶加載

  其實(shí)我們可以走的更遠(yuǎn),對(duì)于非必須模塊不僅僅可以延遲它的執(zhí)行,甚至可以延遲它的加載。

  但問(wèn)題是我們?nèi)绾螞Q定一個(gè)模塊是必須還是非必須呢,最恰當(dāng)莫過(guò)取決于用戶使用這個(gè)模塊的概率有多少。Faceboook早在09年的時(shí)候就已經(jīng)注意到這個(gè)問(wèn)題:Frontend Performance Engineering in Facebook : Velocity 2009,只不過(guò)他們是以樣式碎片來(lái)引出這個(gè)問(wèn)題。

  假設(shè)我們需要在頁(yè)面上加入A、B、C三個(gè)功能,意味著我們需要引入A、B、C對(duì)應(yīng)的html片段和樣式碎片(暫不考慮js),并且最終把三個(gè)功能樣式碎片在上線前壓縮到同一個(gè)文件中。但可能過(guò)了相當(dāng)長(zhǎng)時(shí)間,我們移除了A功能,但這個(gè)時(shí)候大概不會(huì)有人記得也把關(guān)于A功能的樣式從上線樣式中移除。久而久之冗余的代碼會(huì)變得越來(lái)越多。Facebook引入了一套靜態(tài)資源管理方案(Static Resource Management)來(lái)解決這個(gè)問(wèn)題:

  具體來(lái)說(shuō)是將樣式的“聲明”(Declaration)和請(qǐng)求(Delivery)請(qǐng)求,并且是否請(qǐng)求一個(gè)樣式由是否擁有該功能的 html片段決定。

  當(dāng)然同時(shí)也考慮也會(huì)適當(dāng)?shù)暮喜邮狡危@完全是基于使用算法對(duì)用戶使用模塊情況進(jìn)行分析,挑選出使用頻率比較高的模塊進(jìn)行拼合。

  這一套系統(tǒng)不僅僅是對(duì)樣式碎片,對(duì)js,對(duì)圖片sprites的拼合同樣有效。

  你會(huì)不會(huì)覺得我上面說(shuō)的懶加載還是離自己太遠(yuǎn)了? 但然不是,你去看看現(xiàn)在的人人網(wǎng)個(gè)人主頁(yè)看看

  如果你在點(diǎn)擊圖中標(biāo)注的“與我相關(guān)”、“相冊(cè)”、“分享”按鈕并觀察Chrome的Timeline工具,那么都是在點(diǎn)擊之后才加載對(duì)應(yīng)的模塊

  三. Delay Execution

  利用瀏覽器緩存

  腳本最致命的不是加載,而是執(zhí)行。因?yàn)楹螘r(shí)加載畢竟是可控的,甚至可以是異步的,比如通過(guò)調(diào)整外鏈的位置,動(dòng)態(tài)的創(chuàng)建腳本。但一旦腳本加載完成,它就會(huì)被立即執(zhí)行(Evaluate Script),頁(yè)面的渲染也就隨之停止,甚至導(dǎo)致在低端瀏覽器上假死。

  更加充分的理由是,大部分的頁(yè)面不是Single Page Application,不需要依靠腳本來(lái)初始化頁(yè)面。服務(wù)器返回的頁(yè)面是立即可用的,可以想象我們初始化腳本的時(shí)間都花在用戶事件的綁定,頁(yè)面信息的豐滿(用戶信息,個(gè)性推薦)。Steve Souders發(fā)現(xiàn)在Alexa上排名前十的美國(guó)網(wǎng)站上的js代碼,只有29%在window.onload事件之前被調(diào)用,其他的71%的代碼與頁(yè)面的渲染無(wú)關(guān)。

  Steve Souders的ControlJS是我認(rèn)為一直被忽視的一個(gè)加載器,它與Labjs一樣能夠控制的腳本的異步加載,甚至(包括行內(nèi)腳本,但不完美)延遲執(zhí)行。它延遲執(zhí)行腳本的思路非常簡(jiǎn)單:既然只要在頁(yè)面上插入腳本就會(huì)導(dǎo)致腳本的執(zhí)行,那么在需要執(zhí)行的時(shí)候才把腳本插入進(jìn)頁(yè)面。但這樣一來(lái)腳本的加載也被延遲了?不,我們會(huì)通過(guò)其他元素來(lái)提前加載腳本,比如img或者是object標(biāo)簽,或者是非法的mine type的script標(biāo)簽。這樣當(dāng)真正的腳本被插入頁(yè)面時(shí),只會(huì)從緩存中讀取。而不會(huì)發(fā)出新的請(qǐng)求。

  Stoyan Stefanov在它的文章Preload CSS/JavaScript without execution中詳細(xì)描述了這個(gè)技巧,   如果判斷瀏覽器是IE就是用image標(biāo)簽,如果是其他瀏覽器,則使用object元素:

window.onload = function () {
    var i = 0,
        max = 0,
        o = null,
        preload = [
            // list of stuff to preload    
        ],
        isIE = navigator.appName.indexOf('Microsoft') === 0;
    for (i = 0, max = preload.length; i < max; i += 1) {
        if (isIE) {
            new Image().src = preload[i];
            continue;
        }
        o = document.createElement('object');
        o.data = preload[i];
        // IE stuff, otherwise 0x0 is OK
        //o.width = 1;
        //o.height = 1;
        //o.style.visibility = "hidden";
        //o.type = "text/plain"; // IE 
        o.width  = 0;
        o.height = 0;
        // only FF appends to the head
        // all others require body
        document.body.appendChild(o);
    }
};

  同時(shí)它還列舉了其他的一些嘗試,但并非對(duì)所有的瀏覽器都有效,比如:

  • 使用<link>元素加載script,這么做在Chrome中的風(fēng)險(xiǎn)是,在當(dāng)前頁(yè)有效,但是在以后打開需要使用該腳本的頁(yè)面會(huì)無(wú)視該文件為緩存

  • 改變script標(biāo)簽外鏈的type值,比如改為text/cache來(lái)阻止腳本的執(zhí)行。這么做會(huì)導(dǎo)致在某些瀏覽器(比如FF3.6)中壓根連請(qǐng)求都不會(huì)發(fā)出

  type=prefetch

  延遲執(zhí)行并非僅僅作為當(dāng)前頁(yè)面的優(yōu)化方案,還可以為用戶可能打開的頁(yè)面提前緩存資源,如果你對(duì)這兩種類型的link元素熟悉的話:

  • <link rel="subresource" href="jquery.js">: subresource類型用于加載當(dāng)前頁(yè)面將使用(但還未使用)的資源(預(yù)先載入緩存中),擁有較高優(yōu)先級(jí)

  • <link rel="prefetch" >: prefetch類型用于加載用戶將會(huì)打開頁(yè)面中使用到的資源,但優(yōu)先級(jí)較低,也就意味著瀏覽器不做保證它能夠加載到你指定的資源。

  那么上一節(jié)延遲執(zhí)行的方案就可以作為subresource與prefeth的回滾方案。同時(shí)還有其他的類型:

  • <link rel="dns-prefetch" href="http://host_name_to_prefetch.com">: dns-prefetch類型用于提前dns解析和緩存域名主機(jī)信息,以確保將來(lái)再請(qǐng)求同域名的資源時(shí)能夠節(jié)省dns查找時(shí)間,比如我們可以看到淘寶首頁(yè)就使用了這個(gè)類型的標(biāo)簽:

  • <link rel="prerender" >: prerender類型就比較霸道了,它告訴瀏覽器打開一個(gè)新的標(biāo)簽頁(yè)(但不可見)來(lái)渲染指定頁(yè)面,比如這個(gè)頁(yè)面:

  這也就意味著如果用戶真的訪問(wèn)到該頁(yè)面時(shí),就會(huì)有“秒開”的用戶體驗(yàn)。

  但現(xiàn)實(shí)并非那么美好,首先你如何能預(yù)測(cè)用戶打開的頁(yè)面呢,這個(gè)功能更適合閱讀或者論壇類型的網(wǎng)站,因?yàn)橛脩粲泻艽蟮母怕蕰?huì)往下翻頁(yè);要注意提前的渲染頁(yè)面的網(wǎng)絡(luò)請(qǐng)求和優(yōu)先級(jí)和GPU使用權(quán)限優(yōu)先級(jí)都比其他頁(yè)面的要低,瀏覽器對(duì)提前渲染頁(yè)面類型也有一定的要求,具體可以參考這里

  利用LocalStorage

  在聊如何用它來(lái)解決我們遇到的問(wèn)題之前,個(gè)人覺得首先應(yīng)該聊聊它的優(yōu)勢(shì)和劣勢(shì)。

  Chris Heilmann在文章There is no simple solution for local storage中指出了一些常見的LS劣勢(shì),比如同步時(shí)可能會(huì)阻塞頁(yè)面的渲染、I/O操作會(huì)引起不確定的延時(shí)、持久化機(jī)制會(huì)導(dǎo)致冗余的數(shù)據(jù)等。雖然Chirs在文章中用到了比如"terrible performance", "slow"等字眼,但卻沒有真正的指出究竟是具體的哪一項(xiàng)操作導(dǎo)致了性能的低下。

  Nicholas C. Zakas于是寫了一篇針對(duì)該文的文章In defense of localStorage,從文章的名字就可以看出,Nicholas想要捍衛(wèi)LS,畢竟它不是在上一文章中被描述的那樣一無(wú)是處,不應(yīng)該被抵制。

  比較性能這種事情,應(yīng)該看怎么比,和誰(shuí)比。

  就“讀”數(shù)據(jù)而言,如果你把“從LS中讀一個(gè)值”和“從Object對(duì)象中讀一個(gè)屬性”相比,是不公平的,前者是從硬盤里讀,后者是從內(nèi)存里讀,就好比讓汽車與飛機(jī)賽跑一樣,有一個(gè)benchmark各位可以參考一下:localStorage vs. Objects:

  跑分的標(biāo)準(zhǔn)是OPS(operation per second),值當(dāng)然是越高越好。你可能會(huì)注意到,在某個(gè)瀏覽器的對(duì)比列中,沒有顯示關(guān)于LS的紅色列——這不是因?yàn)榻y(tǒng)計(jì)出錯(cuò),而是因?yàn)長(zhǎng)S的操作性能太差,跑分太低(相對(duì)從Object中讀取屬性而言),所以無(wú)法顯示在同一張表格內(nèi),如果你真的想看的話,可以給你看一張放大的版本:

  這樣以來(lái)你大概就知道兩者在什么級(jí)別上了。

  在瀏覽器中與LS最相近的機(jī)制莫過(guò)于Cookie了:Cookie同樣以key-value的形式進(jìn)行存儲(chǔ),同樣需要進(jìn)行I/O操作,同樣需要對(duì)不同的tab標(biāo)簽進(jìn)行同步。同樣有benchmark可以供我們進(jìn)行參考:localStorage vs. Cookies

  從Brwoserscope中提供的結(jié)果可以看出,就Reading from cookie, Reading from localStorage getItem, Writing to cookie,Writing to localStorage property四項(xiàng)操作而言,在不同瀏覽器不同平臺(tái),讀和寫的效率都不太相同,有的趨于一致,有的大相徑庭。

  甚至就LS自己而言,不同的存儲(chǔ)方式和不同的讀取方式也會(huì)產(chǎn)生效率方面的問(wèn)題。有兩個(gè)benchmark非常值得說(shuō)明問(wèn)題:

  1. localStorage-string-size

  2. localStorage String Size Retrieval

  在第一個(gè)測(cè)試中,Nicholas在LS中用四個(gè)key分別存儲(chǔ)了100個(gè)字符,500個(gè)字符,1000個(gè)字符和2000個(gè)字符。測(cè)試分別讀取不同長(zhǎng)度字符的速度。結(jié)果是:讀取速度與讀取字符的長(zhǎng)度無(wú)關(guān)

  第二個(gè)測(cè)試用于測(cè)試讀取1000個(gè)字符的速度,不同的是對(duì)照組是一次性讀取1000個(gè)字符;而實(shí)驗(yàn)組是從10個(gè)key中(每個(gè)key存儲(chǔ)100個(gè)字符)分10次讀取。結(jié)論: 是分10此讀取的速度會(huì)比一次性讀取慢90%左右

  LS也并非沒有痛點(diǎn)。大部分的LS都是基于同一個(gè)域名共享存儲(chǔ)數(shù)據(jù),所以當(dāng)你在多個(gè)標(biāo)簽打開同一個(gè)域名下的站點(diǎn)時(shí),必須面臨一個(gè)同步的問(wèn)題,當(dāng)A標(biāo)簽想寫入LS與B標(biāo)簽想從LS中讀同時(shí)發(fā)生時(shí),哪一個(gè)操作應(yīng)該首先發(fā)生?為了保證數(shù)據(jù)的一致性,在讀或者在寫時(shí) 務(wù)必會(huì)把LS鎖住(甚至在操作系統(tǒng)安裝的殺毒軟件在掃描到該文件時(shí),會(huì)暫時(shí)鎖住該文件)。因?yàn)閱尉€程的關(guān)系,在等待LS I/O操作的同時(shí),UI線程和Javascript也無(wú)法被執(zhí)行。

  但實(shí)際情況遠(yuǎn)比我們想象的復(fù)雜的多。為了提高讀寫的速度,某些瀏覽器(比如火狐)會(huì)在加載頁(yè)面時(shí)就把該域名下LS數(shù)據(jù)加載入內(nèi)存中,這么做的副作用是延遲了頁(yè)面的加載速度。但如果不這么做而是在臨時(shí)讀寫LS時(shí)再加載,同樣有死鎖瀏覽器的風(fēng)險(xiǎn)。并且把數(shù)據(jù)載入內(nèi)存中也面臨著將內(nèi)存同步至硬盤的問(wèn)題。

  上面說(shuō)到的這些問(wèn)題大部分歸咎于內(nèi)部的實(shí)現(xiàn),需要依賴瀏覽器開發(fā)者來(lái)改進(jìn)。并且并非僅僅存在于LS中,相信在IndexedDB、webSQL甚至Cookie中也有類似的問(wèn)題在發(fā)生。

  實(shí)戰(zhàn)開始

  考慮到移動(dòng)端網(wǎng)絡(luò)環(huán)境的不穩(wěn)定,為了避免網(wǎng)絡(luò)延遲(network latency),大部分網(wǎng)站的移動(dòng)端站點(diǎn)會(huì)將體積龐大的類庫(kù)存儲(chǔ)于本地瀏覽器的LS中。但百度音樂(lè)將這個(gè)技術(shù)也應(yīng)用到了PC端,他們將所依賴的jQuery類庫(kù)存入LS中。用一段很簡(jiǎn)單的代碼來(lái)保證對(duì)jQuery的正確載入。我們一起來(lái)看看這段代碼。代碼詳解就書寫在注釋中了:

!function (globals, document) {
    var storagePrefix = "mbox_";
    globals.LocalJs = {
        require: function (file, callback) {
            /*
                如果無(wú)法使用localstorage,則使用document.write把需要請(qǐng)求的腳本寫在頁(yè)面上
                作為fallback,使用document.write確保已經(jīng)加載了所需要的類庫(kù)
            */
            if (!localStorage.getItem(storagePrefix + "jq")) {
                document.write('<script src="http://www.vxbq.cn/uploadfile/cj/20140920/' + file + '" type="text/javascript"></script>');
                var self = this;
            /*
                并且3s后再請(qǐng)求一次,但這次請(qǐng)求的目的是為了獲取jquery源碼,寫入localstorage中(見下方的_loadjs函數(shù))
                這次“一定”走緩存,不會(huì)發(fā)出多余的請(qǐng)求
                為什么會(huì)延遲3s執(zhí)行?為了確保通過(guò)document.write請(qǐng)求jQuery已經(jīng)加載完成。但很明顯3s也并非一個(gè)保險(xiǎn)的數(shù)值
                同時(shí)使用document.write也是出于需要故意阻塞的原因,而無(wú)法為其添加回調(diào),所以延時(shí)3s
            */
                setTimeout(function () {
                    self._loadJs(file, callback)
                }, 3e3)
            } else {
                // 如果可以使用localstorage,則執(zhí)行注入
                this._reject(localStorage.getItem(storagePrefix + "jq"), callback)
            }
        },
        _loadJs: function (file, callback) {
            if (!file) {
                return false
            }
            var self = this;
            var xhr = new XMLHttpRequest;
            xhr.open("GET", file);
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        localStorage.setItem(storagePrefix + "jq", xhr.responseText)
                    } else {}
                }
            };
            xhr.send()
        },
        _reject: function (data, callback) {
            var el = document.createElement("script");
            el.type = "text/javascript";
            /*
                關(guān)于如何執(zhí)行LS中的源碼,我們有三種方式
                1. eval
                2. new Function
                3. 在一段script標(biāo)簽中插入源碼,再將該script標(biāo)簽插入頁(yè)碼中
                關(guān)于這三種方式的執(zhí)行效率,我們內(nèi)部初步測(cè)試的結(jié)果是不同的瀏覽器下效率各不相同
                參考一些jsperf上的測(cè)試,執(zhí)行效率甚至和具體代碼有關(guān)。
            */
            el.appendChild(document.createTextNode(data));
            document.getElementsByTagName("head")[0].appendChild(el);
            callback && callback()
        },
        isSupport: function () {
            return window.localStorage
        }
    }
}(window, document);
!
function () {
    var url = _GET_HASHMAP ? _GET_HASHMAP("/player/static/js/naga/common/jquery-1.7.2.js") : "/player/static/js/naga/common/jquery-1.7.2.js";
    url = url.replace(/^//mu[0-9]*.bdstatic.com/g, "");
    LocalJs.require(url, function () {})
}(); 

  因?yàn)樽烂娑说臑g覽器兼容性問(wèn)題比移動(dòng)端會(huì)嚴(yán)峻的多,所以大多數(shù)對(duì)LS利用屬于“做加法”,或者“輕量級(jí)”的應(yīng)用。最后一瞥不同站點(diǎn)在PC平臺(tái)的對(duì)LS的使用情況:

    • 比如百度和github用LS記錄用戶的搜素行為,為了提供更好的搜索建議

    • Twitter利用LS最主要的記錄了與用戶關(guān)聯(lián)的信息(截圖自我的Twitter賬號(hào),因?yàn)殛P(guān)注者和被關(guān)注者的不同數(shù)據(jù)會(huì)有差異):
    • userAdjacencyList表占40,158 bytes,用于記錄每個(gè)字關(guān)聯(lián)的用戶信息
    • userHash表占36,883 bytes,用于記錄用戶被關(guān)注的人信息

    • Google利用LS記錄了樣式:

    • 天貓用LS記錄了導(dǎo)航欄的HTML碎片代碼:

  總結(jié)

  No silver bullet.沒有任何一項(xiàng)技術(shù)或者方案是萬(wàn)能的,雖然開源社區(qū)和瀏覽器廠商在提供給我們?cè)絹?lái)越豐富的資源,但并不意味著今后遇見的問(wèn)題就會(huì)越來(lái)越少。相反,或許正因?yàn)槎鄻有裕桶l(fā)展中技術(shù)的不完善,事情會(huì)變得更復(fù)雜,我們?cè)谶x擇時(shí)要權(quán)衡更多。我無(wú)意去推崇某一項(xiàng)解決方案,我想盡可能多的把這些方案與這些方案的厲害呈現(xiàn)給大家,畢竟不同人考慮問(wèn)題的方面不同,業(yè)務(wù)需求不同。

  還有一個(gè)問(wèn)題是,本文描述的大部分技術(shù)都是針對(duì)現(xiàn)代瀏覽器而言,那么如何應(yīng)對(duì)低端瀏覽器呢?

  從百度統(tǒng)計(jì)這張17個(gè)月的瀏覽器市場(chǎng)份額圖中可以看出(當(dāng)然可能因?yàn)椴煌军c(diǎn)的用戶特征不同會(huì)導(dǎo)致使用的瀏覽器分布與上圖有出入),我們最關(guān)心的IE6的市場(chǎng)份額一直是呈現(xiàn)的是下滑的趨勢(shì),目前已經(jīng)降至幾乎與IE9持平;而IE9在今年的市場(chǎng)份額也一直穩(wěn)步上升;IE7已經(jīng)被遙遙甩在身后。領(lǐng)頭的IE8與Chrome明顯讓我們感受到有足夠的信心去嘗試新的技術(shù)。還等什么,行動(dòng)起來(lái)吧!

  其他參考文獻(xiàn)

  • Chrome’s preloader delivers a ~20% speed improvement!
  • High Performance Networking in Google Chrome
  • The real conflict behind and @srcset
  • How the Browser Pre-loader Makes Pages Load Faster
  • Script downloading in Chrome
  • Who’s Afraid of the Big Bad Preloader?
  • localStorage Read Performance
  • The performance of localStorage revisited
  • Storager case study: Bing, Google
  • Measuring localStorage Performance
  • Application Cache is a Douchebag
生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 亚洲综合春色另类久久 | 欧美一区二区三区四区五区六区 | 国产成人精品日本亚洲语言 | 中文精品久久久久国产网站 | 日本欧美做爰全免费的视频 | 伊人2222| 无国产精品白浆免费视 | 国产在线播放成人免费 | 欧美日韩在线观看视频 | 亚洲色欲色欲综合网站 | 国产精品欧美在线不卡 | 日日夜夜精品免费视频 | 麻豆亚洲精品一区二区 | 久久这里都是精品 | 欧美一级视频在线高清观看 | yy一级毛片免费视频 | 成人a毛片久久免费播放 | 亚洲欧美色一区二区三区 | 日本欧美一区二区三区免费不卡 | 精品国产免费一区二区三区五区 | 午夜影视福利 | 国产热视频 | 三级中文字幕永久在线视频 | 韩日一区二区 | 久久精品伊人 | 免费观看福利视频 | 中文字幕35页 | 中文字幕一区二区三区免费看 | 男女羞羞视频网站 | 视频一区二区国产无限在线观看 | 中文乱码字字幕在线第5页 中文欧美日韩 | 欧美日韩亚洲高清老妇性 | 亚洲春色视频 | 美国福利视频 | 香蕉人人超 | 99精品大香线蕉线伊人久久久 | 免费福利影院 | 在线 | 一区二区三区四区 | 五月婷婷视频在线观看 | 亚洲视频在线观看免费视频 | 日韩欧美亚洲国产 |