做者 李光毅 發佈於 2014年6月27日javascript
幾乎每個前端程序員都知道應該把script標籤放在頁面底部。關於這個經典的論述能夠追溯到Nicholas的 High Performance Javasript 這本書的第一章Loading and Execution中,他之因此建議這麼作是由於:php
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.css
簡而言之,若是瀏覽器加載並執行腳本,會引發頁面的渲染被暫停,甚至還會阻塞其餘資源(好比圖片)的加載。爲了更快的給用戶呈現網頁內容,更好的用戶體驗,應該把腳本放在頁面底部,使之最後加載。html
爲何要在標題中使用「再」這個字?由於在工做中逐漸發現,咱們常常談論的一些頁面優化技巧,好比上面所說的老是把腳本放在頁面的底部,壓縮合並樣式或者腳本文件等,時至今日已再也不是最佳的解決方案,甚至事與願違,轉化爲性能的毒藥。這篇文章所要聊的,即是展現某些不被人關注的瀏覽器特性或者技巧,來繼續完成資源加載性能優化的任務。前端
首先讓咱們看一看這樣一類資源分佈的頁面:java
<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>
這類頁面的特色是,一個外鏈腳本置於頁面頭部,三個外鏈腳本置於頁面的底部,而且是故意跟隨在一系列img以後,在Chrome中頁面加載的網絡請求瀑布圖以下:jquery
值得注意的是,雖然腳本放置在圖片以後,但加載仍先於圖片。爲何會出現這樣的狀況?爲何故意置後資源可以提早獲得加載?
雖然瀏覽器引擎的實現不一樣,但原理都十分的近似。不一樣瀏覽器的製造廠商們(vendor)很是清楚瀏覽器的瓶頸在哪(好比network, javascript evaluate, reflow, repaint)。針對這些問題,瀏覽器也在不斷的進化,因此咱們才能看到更快的腳本引擎,調用GPU的渲染等一推陳出新的優化技術和方案。
一樣在資源加載上,早在IE8開始,一種叫作lookahead pre-parser(在Chrome中稱爲preloader)的機制就已經開始在不一樣瀏覽器中興起。IE8相對於以前IE版本的提高除了將每臺host最高並行下載的資源數從2提高至6,而且可以容許並行下載腳本文件以外,最後就是這個lookahead pre-parser機制
但我仍是沒有詳述這是一個什麼樣的機制,不着急,首先看看與IE7的對比:
以上面的頁面爲例,咱們看看IE7下的瀑布圖:
底部的腳本並無提早被加載,而且由於因爲單個域名最高並行下載數2的限制,資源老是兩個兩個很整齊的錯開並行下載。
但在IE8下,很明顯底部腳本又被提早:
並無統一的標準規定這套機制應具有何種功能已經如何實現。但你能夠大體這麼理解:瀏覽器一般會準備兩個頁面解析器parser,一個(main parser)用於正常的頁面解析,而另外一個(preloader)則試圖去文檔中搜尋更多須要加載的資源,但這裏的資源一般僅限於外鏈的js、stylesheet、image;不包括audio、video等。而且動態插入頁面的資源無效。
但細節方面卻值得注意:
好比關於preloader的觸發時機,並不是與解析頁面同時開始,而一般是在加載某個head中的外鏈腳本阻塞了main parser的狀況下才啓動;
也不是全部瀏覽器的preloader會把圖片列爲預加載的資源,可能它認爲圖片加載過於耗費帶寬而不把它列爲預加載資源之列;
preloader也並不是最優,在某些瀏覽器中它會阻塞body的解析。由於有的瀏覽器將頁面文檔拆分爲head和body兩部分進行解析,在head沒有解析完以前,body不會被解析。一旦在解析head的過程當中觸發了preloader,這無疑會致使head的解析時間過長。
preloader的誕生本是出於一番好意,但好心也有可能辦壞事。
filamentgroup有一種著名的響應式設計的圖片解決方案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>
它的工做原理是,當responsive-images.js加載完成時,它會檢測當前顯示器的尺寸,而且設置一個cookie來標記當前尺寸。同時你須要在服務器端準備一個.htaccess文件,接下來當你請求圖片時,.htaccess中的配置會檢測隨圖片請求異同發送的Cookie是被設置成medium仍是large,這樣也就保證根據顯示器的尺寸來加載對於的圖片大小。
很明顯這個方案成功的前提是,js執行先於發出圖片請求。但在Chrome下打開,你會發現執行順序是這樣:
responsive-images.js和圖片幾乎是同一時間發出的請求。結果是第一次打開頁面給出的是默認小圖,若是你再次刷新頁面,由於Cookie才設置成功,服務器返回的是大圖。
嚴格意義上來講在某些瀏覽器中這不必定是preloader引發的問題,但preloader引發的問題相似:插入腳本的順序和位置或許是開發者有意而爲之的,但preloader的這種「聰明」卻可能違背開發者的意圖,形成誤差。
若是你以爲上一個例子還不夠說明問題的話,最後請考慮使用picture(或者@srcset)元素的狀況:
<picture> <source src="med.jpg" media="(min-width: 40em)" /> <source src="sm.jpg"/> <img src="fallback.jpg" alt="" /> </picture>
在preloader搜尋到該元素而且試圖去下載該資源時,它應該怎麼辦?一個正常的paser應該是在解析該元素時根據當時頁面的渲染布局去下載,而當時這類工做不必定已經完成,preloader只是提早找到了該元素。退一步來講,即便不考慮頁面渲染的狀況,假設preloader在這種情形下會觸發一種默認加載策略,那應該是"mobile first"仍是"desktop first"?默認應該加載高清仍是低清照片?
理想是豐滿的,現實是骨感的。出於種種的緣由,咱們幾乎從不直接在頁面上插入js腳本,而是使用第三方的加載器,好比seajs或者requirejs。關於使用加載器和模塊化開發的優點在這裏再也不贅述。但我想回到原點,討論應該如何利用加載器,就從seajs與requirejs的不一樣聊起。
在開始以前我已經假設你對requirejs與seajs語法已經基本熟悉了,若是尚未,請移步這裏:
BTW: 若是你仍是習慣在部署上線前把全部js文件合併打包成一個文件,那麼seajs和requirejs其實對你來講並沒有區別。
seajs與requirejs在模塊的加載方面是沒有差別的,不管是requirejs在定義模塊時定義的依賴模塊,仍是seajs在factory函數中require的依賴模塊,在會在加載當前模塊時被載入,異步,而且順序不可控。差別在於factory函數執行的時機。
爲了加強對比,咱們在定義依賴模塊的時候,故意讓它們的factory函數要執行至關長的時間,好比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;此處會連續執行1s exports.foo = function() { console.log("A"); } })
爲了加強對比,設置了三組進行對照試驗,分別是:
//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){ }
接下來咱們看看代碼執行的瀑布圖:
1.require.js:在加載完依賴模塊以後當即執行了該模塊的factory函數
2.sea.js: 下面兩張圖應該放在一塊兒比較。兩處代碼都同時加載了依賴模塊,但由於沒有require的關係,第三張圖中沒有像第二張圖那樣執行耗時的factory函數。可見seajs執行的原則正如CMD標準中所述Execution must be lazy。
我想進一步表達的是,不管requirejs和seajs,一般來講大部分的邏輯代碼都會放在模塊的factory函數中,因此factory函數執行的代價是很是大的。但上圖也一樣告訴咱們模塊的define,甚至模塊文件的Evaluate代價很是小,與factory函數無關。因此咱們是否是應該儘量的避免執行factory函數,或者等到咱們須要的指定功能的時候才執行對應的factory函數?好比:
document.body.onclick = function () { require(some_kind_of_module); }
這是很是實際的問題,好比愛奇藝一個視頻播放的頁面,咱們有沒有必要在第一屏加載頁面的時候就加載登錄註冊,或者評論,或者分享功能呢?由於有很是大的可能用戶只是來這裏看這個視頻,直至看完視頻它都不會用到登錄註冊功能,也不會去分享這個視頻等。加載這些功能不只僅對瀏覽器是一個負擔,還有可能調用後臺的接口,這樣的性能消耗是很是可觀的。
咱們能夠把這樣稱之爲"懶執行"。雖然seajs並不是有意實現如上所說的「懶執行」(它只是在儘量遵循CommonJS標準靠近)。但「懶執行」確實可以有助於提高一部分性能。
但也有人會對此產生顧慮。
記得玉伯轉過的一個帖子:SeaJS與RequireJS最大的區別。咱們看看其中反對這麼作的人的觀點:
我我的感受requirejs更科學,全部依賴的模塊要先執行好。若是A模塊依賴B。當執行A中的某個操doSomething()後,再去依賴執行B模塊require('B');若是B模塊出錯了,doSomething的操做如何回滾? 不少語言中的import, include, useing都是先將導入的類或者模塊執行好。若是被導入的模塊都有問題,有錯誤,執行當前模塊有何意義?
而依賴dependencies是工廠的原材料,在工廠進行生產的時候,是先把原材料一次性都在它本身的工廠里加工好,仍是把原材料的工廠搬到當前的factory來何時須要,何時加工,哪一個總體時間效率更高?
首先回答第一個問題。
第一個問題的題設並不徹底正確,「依賴」和「執行」的概念比較模糊。編程語言執行一般分爲兩個階段,編譯(compilation)和運行(runtime)。對於靜態語言(好比C/C++)來講,在編譯時若是出現錯誤,那可能以前的編譯都視爲無效,的確會出現描述中須要回滾或者從新編譯的問題。但對於動態語言或者腳本語言,大部分執行都處在運行時階段或者解釋器中:假設我使用Nodejs或者Python寫了一段服務器運行腳本,在持續運行了一段時間以後由於某項需求要加載某個(依賴)模塊,同時也由於這個模塊致使服務端掛了——我認爲這時並不存在回滾的問題。在加載依賴模塊以前當前的模塊的大部分功能已經成功運行了。
再回答第二個問題。
對於「工廠」和「原材料」的比喻不夠恰當。難道依賴模塊沒有加載完畢當前模塊就沒法工做嗎?requirejs的確是這樣的,從上面的截圖能夠看出,依賴模塊老是先於當前模塊加載和執行完畢。但咱們考慮一下基於CommonJS標準的Nodejs的語法,使用require函數加載依賴模塊能夠在頁面的任何位置,能夠只是在須要的時候。也就是說當前模塊沒必要在依賴模塊加載完畢後才執行。
你可能會問,爲何要拿AMD標準與CommonJS標準比較,而不是CMD標準?
玉伯在CommonJS 是什麼這篇文章中已經告訴了咱們CMD某種程度上遵循的就是CommonJS標準:
從上面能夠看出,Sea.js 的初衷是爲了讓 CommonJS Modules/1.1 的模塊能運行在瀏覽器端,但因爲瀏覽器和服務器的實質差別,實際上這個夢沒法徹底達成,也沒有必要去達成。
更好的一種方式是,Sea.js 專一於 Web 瀏覽器端,CommonJS 則專一於服務器端,但二者有共通的部分。對於須要在兩端均可以跑的模塊,能夠 有便捷的方案來快速遷移。
其實AMD標準的推出同時也是遵循CommonJS,在requirejs官方文檔的COMMONJS NOTES中說道:
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固然是一個理想的標準,但至少現階段對瀏覽器來講還不夠友好,因此纔會出現AMD與CMD,其實他們都是在作同一件事,就是致力於前端代碼更友好的模塊化。因此我的認爲依賴模塊的加載和執行在不一樣標準下實現不一樣,能夠理解爲在用不一樣的方式在完成同一個目標, 並非一件太值得過於糾結的事。
其實咱們能夠走的更遠,對於非必須模塊不只僅能夠延遲它的執行,甚至能夠延遲它的加載。
但問題是咱們如何決定一個模塊是必須仍是非必須呢,最恰當莫過取決於用戶使用這個模塊的機率有多少。Faceboook早在09年的時候就已經注意到這個問題:Frontend Performance Engineering in Facebook : Velocity 2009,只不過他們是以樣式碎片來引出這個問題。
假設咱們須要在頁面上加入A、B、C三個功能,意味着咱們須要引入A、B、C對應的html片斷和樣式碎片(暫不考慮js),而且最終把三個功能樣式碎片在上線前壓縮到同一個文件中。但可能過了至關長時間,咱們移除了A功能,但這個時候大概不會有人記得也把關於A功能的樣式從上線樣式中移除。長此以往冗餘的代碼會變得愈來愈多。Facebook引入了一套靜態資源管理方案(Static Resource Management)來解決這個問題:
具體來講是將樣式的「聲明」(Declaration)和請求(Delivery)請求,而且是否請求一個樣式由是否擁有該功能的 html片斷決定。
固然同時也考慮也會適當的合併樣式片斷,但這徹底是基於使用算法對用戶使用模塊狀況進行分析,挑選出使用頻率比較高的模塊進行拼合。
這一套系統不只僅是對樣式碎片,對js,對圖片sprites的拼合一樣有效。
你會不會以爲我上面說的懶加載仍是離本身太遠了? 但然不是,你去看看如今的人人網我的主頁看看
若是你在點擊圖中標註的「與我相關」、「相冊」、「分享」按鈕並觀察Chrome的Timeline工具,那麼都是在點擊以後才加載對應的模塊
腳本最致命的不是加載,而是執行。由於什麼時候加載畢竟是可控的,甚至能夠是異步的,好比經過調整外鏈的位置,動態的建立腳本。但一旦腳本加載完成,它就會被當即執行(Evaluate Script),頁面的渲染也就隨之中止,甚至致使在低端瀏覽器上假死。
更加充分的理由是,大部分的頁面不是Single Page Application,不須要依靠腳原本初始化頁面。服務器返回的頁面是當即可用的,能夠想象咱們初始化腳本的時間都花在用戶事件的綁定,頁面信息的豐滿(用戶信息,個性推薦)。Steve Souders發如今Alexa上排名前十的美國網站上的js代碼,只有29%在window.onload事件以前被調用,其餘的71%的代碼與頁面的渲染無關。
Steve Souders的ControlJS是我認爲一直被忽視的一個加載器,它與Labjs同樣可以控制的腳本的異步加載,甚至(包括行內腳本,但不完美)延遲執行。它延遲執行腳本的思路很是簡單:既然只要在頁面上插入腳本就會致使腳本的執行,那麼在須要執行的時候才把腳本插入進頁面。但這樣一來腳本的加載也被延遲了?不,咱們會經過其餘元素來提早加載腳本,好比img或者是object標籤,或者是非法的mine type的script標籤。這樣當真正的腳本被插入頁面時,只會從緩存中讀取。而不會發出新的請求。
Stoyan Stefanov在它的文章Preload CSS/JavaScript without execution中詳細描述了這個技巧, 若是判斷瀏覽器是IE就是用image標籤,若是是其餘瀏覽器,則使用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); } };
同時它還列舉了其餘的一些嘗試,但並不是對全部的瀏覽器都有效,好比:
使用<link>元素加載script,這麼作在Chrome中的風險是,在當前頁有效,可是在之後打開須要使用該腳本的頁面會無視該文件爲緩存
改變script標籤外鏈的type值,好比改成text/cache來阻止腳本的執行。這麼作會致使在某些瀏覽器(好比FF3.6)中壓根連請求都不會發出
延遲執行並不是僅僅做爲當前頁面的優化方案,還能夠爲用戶可能打開的頁面提早緩存資源,若是你對這兩種類型的link元素熟悉的話:
<link rel="subresource" href="jquery.js">: subresource類型用於加載當前頁面將使用(但還未使用)的資源(預先載入緩存中),擁有較高優先級
<link rel="prefetch" href="http://NextPage.html">: prefetch類型用於加載用戶將會打開頁面中使用到的資源,但優先級較低,也就意味着瀏覽器不作保證它可以加載到你指定的資源。
那麼上一節延遲執行的方案就能夠做爲subresource與prefeth的回滾方案。同時還有其餘的類型:
這也就意味着若是用戶真的訪問到該頁面時,就會有「秒開」的用戶體驗。
但現實並不是那麼美好,首先你如何能預測用戶打開的頁面呢,這個功能更適合閱讀或者論壇類型的網站,由於用戶有很大的機率會往下翻頁;要注意提早的渲染頁面的網絡請求和優先級和GPU使用權限優先級都比其餘頁面的要低,瀏覽器對提早渲染頁面類型也有必定的要求,具體能夠參考這裏
在聊如何用它來解決咱們遇到的問題以前,我的以爲首先應該聊聊它的優點和劣勢。
Chris Heilmann在文章There is no simple solution for local storage中指出了一些常見的LS劣勢,好比同步時可能會阻塞頁面的渲染、I/O操做會引發不肯定的延時、持久化機制會致使冗餘的數據等。雖然Chirs在文章中用到了好比"terrible performance", "slow"等字眼,但卻沒有真正的指出到底是具體的哪一項操做致使了性能的低下。
Nicholas C. Zakas因而寫了一篇針對該文的文章In defense of localStorage,從文章的名字就能夠看出,Nicholas想要捍衛LS,畢竟它不是在上一文章中被描述的那樣一無可取,不該該被抵制。
比較性能這種事情,應該看怎麼比,和誰比。
就「讀」數據而言,若是你把「從LS中讀一個值」和「從Object對象中讀一個屬性」相比,是不公平的,前者是從硬盤裏讀,後者是從內存裏讀,就比如讓汽車與飛機賽跑同樣,有一個benchmark各位能夠參考一下:localStorage vs. Objects:
跑分的標準是OPS(operation per second),值固然是越高越好。你可能會注意到,在某個瀏覽器的對比列中,沒有顯示關於LS的紅色列——這不是由於統計出錯,而是由於LS的操做性能太差,跑分過低(相對從Object中讀取屬性而言),因此沒法顯示在同一張表格內,若是你真的想看的話,能夠給你看一張放大的版本:
這樣以來你大概就知道二者在什麼級別上了。
在瀏覽器中與LS最相近的機制莫過於Cookie了:Cookie一樣以key-value的形式進行存儲,一樣須要進行I/O操做,一樣須要對不一樣的tab標籤進行同步。一樣有benchmark能夠供咱們進行參考:localStorage vs. Cookies
從Brwoserscope中提供的結果能夠看出,就Reading from cookie, Reading from localStorage getItem, Writing to cookie,Writing to localStorage property四項操做而言,在不一樣瀏覽器不一樣平臺,讀和寫的效率都不太相同,有的趨於一致,有的截然不同。
甚至就LS本身而言,不一樣的存儲方式和不一樣的讀取方式也會產生效率方面的問題。有兩個benchmark很是值得說明問題:
在第一個測試中,Nicholas在LS中用四個key分別存儲了100個字符,500個字符,1000個字符和2000個字符。測試分別讀取不一樣長度字符的速度。結果是:讀取速度與讀取字符的長度無關。
第二個測試用於測試讀取1000個字符的速度,不一樣的是對照組是一次性讀取1000個字符;而實驗組是從10個key中(每一個key存儲100個字符)分10次讀取。結論: 是分10此讀取的速度會比一次性讀取慢90%左右。
LS也並不是沒有痛點。大部分的LS都是基於同一個域名共享存儲數據,因此當你在多個標籤打開同一個域名下的站點時,必須面臨一個同步的問題,當A標籤想寫入LS與B標籤想從LS中讀同時發生時,哪個操做應該首先發生?爲了保證數據的一致性,在讀或者在寫時 務必會把LS鎖住(甚至在操做系統安裝的殺毒軟件在掃描到該文件時,會暫時鎖住該文件)。由於單線程的關係,在等待LS I/O操做的同時,UI線程和Javascript也沒法被執行。
但實際狀況遠比咱們想象的複雜的多。爲了提升讀寫的速度,某些瀏覽器(好比火狐)會在加載頁面時就把該域名下LS數據加載入內存中,這麼作的反作用是延遲了頁面的加載速度。但若是不這麼作而是在臨時讀寫LS時再加載,一樣有死鎖瀏覽器的風險。而且把數據載入內存中也面臨着將內存同步至硬盤的問題。
上面說到的這些問題大部分歸咎於內部的實現,須要依賴瀏覽器開發者來改進。而且並不是僅僅存在於LS中,相信在IndexedDB、webSQL甚至Cookie中也有相似的問題在發生。
考慮到移動端網絡環境的不穩定,爲了不網絡延遲(network latency),大部分網站的移動端站點會將體積龐大的類庫存儲於本地瀏覽器的LS中。但百度音樂將這個技術也應用到了PC端,他們將所依賴的jQuery類庫存入LS中。用一段很簡單的代碼來保證對jQuery的正確載入。咱們一塊兒來看看這段代碼。代碼詳解就書寫在註釋中了:
!function (globals, document) { var storagePrefix = "mbox_"; globals.LocalJs = { require: function (file, callback) { /* 若是沒法使用localstorage,則使用document.write把須要請求的腳本寫在頁面上 做爲fallback,使用document.write確保已經加載了所須要的類庫 */ if (!localStorage.getItem(storagePrefix + "jq")) { document.write('<script src="' + file + '" type="text/javascript"></script>'); var self = this; /* 而且3s後再請求一次,但此次請求的目的是爲了獲取jquery源碼,寫入localstorage中(見下方的_loadjs函數) 此次「必定」走緩存,不會發出多餘的請求 爲何會延遲3s執行?爲了確保經過document.write請求jQuery已經加載完成。但很明顯3s也並不是一個保險的數值 同時使用document.write也是出於須要故意阻塞的緣由,而沒法爲其添加回調,因此延時3s */ setTimeout(function () { self._loadJs(file, callback) }, 3e3) } else { // 若是可使用localstorage,則執行注入 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"; /* 關於如何執行LS中的源碼,咱們有三種方式 1. eval 2. new Function 3. 在一段script標籤中插入源碼,再將該script標籤插入頁碼中 關於這三種方式的執行效率,咱們內部初步測試的結果是不一樣的瀏覽器下效率各不相同 參考一些jsperf上的測試,執行效率甚至和具體代碼有關。 */ 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 () {}) }();
由於桌面端的瀏覽器兼容性問題比移動端會嚴峻的多,因此大多數對LS利用屬於「作加法」,或者「輕量級」的應用。最後一瞥不一樣站點在PC平臺的對LS的使用狀況:
No silver bullet.沒有任何一項技術或者方案是萬能的,雖然開源社區和瀏覽器廠商在提供給咱們愈來愈豐富的資源,但並不意味着從此碰見的問題就會愈來愈少。相反,或許正由於多樣性,和發展中技術的不完善,事情會變得更復雜,咱們在選擇時要權衡更多。我無心去推崇某一項解決方案,我想盡量多的把這些方案與這些方案的厲害呈現給你們,畢竟不一樣人考慮問題的方面不一樣,業務需求不一樣。
還有一個問題是,本文描述的大部分技術都是針對現代瀏覽器而言,那麼如何應對低端瀏覽器呢?
從百度統計這張17個月的瀏覽器市場份額圖中能夠看出(固然可能由於不一樣站點的用戶特徵不一樣會致使使用的瀏覽器分佈與上圖有出入),咱們最關心的IE6的市場份額一直是呈現的是下滑的趨勢,目前已經降至幾乎與IE9持平;而IE9在今年的市場份額也一直穩步上升;IE7已經被遙遙甩在身後。領頭的IE8與Chrome明顯讓咱們感覺到有足夠的信心去嘗試新的技術。還等什麼,行動起來吧!
感謝王保平對本文的審校。
來源:
http://www.infoq.com/cn/articles/browser-resource-loading-optimization