Js 問題分析--js 影響頁面性能
現狀分析:問題陳述
分析問題:抽象問題根源,經過實例或推理證實問題的嚴重性
問題引伸:以現有問題爲點開始擴散,這將致使其它什麼問題,或同一類型的問題
問題總結:從分散開始迴歸,再次抽象問題
5.1 DOM 操做不當影響頁面性能
現狀分析:
咱們的頁面上對 DOM 的操做在所不免,不管是焦點圖切換這樣的交互效果,仍是各類
數據接口的應用,DOM 操做都是重要的環節。目前頁面上的 JavaScript 愈來愈多,一旦咱們
的 DOM 操做不當,必然對頁面產生嚴重的性能問題。好比:電腦網報價庫左樹、appendChild
插入 js 的方式,都存在 DOM 操做阻塞整個頁面渲染的問題。
分析問題:
DOM 是文檔對象模型,最主要的 web 前端應用程序接口,是一個獨立於語言的,使用
XML 和 HTML 文檔操做的應用程序接口。
DOM 在瀏覽器中的接口是經過 JavaScript 實現的,客戶端大多數腳本程序都須要與文檔
打交道,使得 DOM 成爲 JavaScript 代碼平常行爲中重要的組成部分。
瀏覽器一般要求 DOM 實現和 JavaScript 實現保持相互獨立。
例如,在 Internet Explorer 中,被稱爲 JScript 的 JavaScript 實現位於庫文件 jscript.dll 中,而
DOM 實現位於另外一個庫 mshtml.dll(內部代號 Trident)。這種分離技術容許其餘技術和語言,如 VBScript,
受益於 Trident 所提供的 DOM 功能和渲染功能。Safari 使用 WebKit 的 WebCore 處理 DOM 和渲染,具備一個
分離的 JavaScriptCore 引擎(最新版本中的綽號是 SquirrelFish)。Google Chrome 也使用 WebKit 的 WebCore
庫渲染頁面,但實現了本身的 JavaScript 引擎 V8。在 Firefox 中,JavaScript 實現採用 Spider-Monkey(最
新版中稱做 TraceMonkey),與其 Gecko 渲染引擎相分離。
摘自《High Performance JavaScript》
兩個獨立的部分以功能接口鏈接就會帶來性能損耗,這意味着每次訪問 DOM 都須要付
出必定代價,你能夠把 DOM 想象成一個島嶼,把 JavaScript 想象成另外一個島嶼,兩個島嶼有
一座橋鏈接,每次過橋都須要支付必定的過橋費,能夠想到,你訪問 DOM 島次數越多,所
支付的過橋費也就越多,因此爲了減小開銷,應該減小訪問 DOM 的次數,儘可能停留在JavaScript 島上。
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/dom.html
問題引伸:
1.HTML 集合:
HTML 元素的集合,例如 document.getElementsByTagName、childNodes 等獲得的都是
HTML 集合,雖然 HTML 集合也有 length 屬性,但它不是數組(由於它們沒有諸如 push()
或 slice()之類的方法),訪問 HTML 集合的 length 比數組的 length 要慢。
遍歷數組比遍歷集合快,若是先將集合元素拷貝到數組,訪問它們的屬性將更快
實在不能轉換成數組,能夠將集合的 length 緩存起來,避免屢次遍歷集合
http://zzb.pcauto.com.cn/power/js/jsProblem/dom2.html
2.循環:
循環向來是程序效率的重中之重,不當的循環會將程序的性能問題放大不少,對於 DOM
操做來講,原本這樣的操做就已經先天不足了,若是再放到某些循環中,帶來的影響將會是
災難性的。
儘可能使用局部變量來緩存 DOM,避免在循環中訪問 DOM 島。
function toArray(coll) {
for (var i = 0, a = [], len = coll.length; i < len; i++) {
a[i] = coll[i];
}
return a;
}
var coll=document.getElementsByTagName("div");
for(var i=0,len=coll.length;i<len;i++){
....
}問題總結:
DOM 是一個獨立存在的 API,獨立使得其能夠提供方便的接口得以操做 DOM,固然這
是以性能爲代價換來的。在瀏覽器中,DOM 提供接口給 JavaScript 用於操做 xhtml 文檔,
JavaScript 解釋型語言的特性加上 DOM 的獨立性,使得操做 DOM 很容易形成頁面的性能瓶
頸。
5.2 首屏 js 阻塞頁面
http://www.gethifi.com/tools/regex
http://zzb.pcauto.com.cn/tools/reg/reg.html
現狀分析:
不管是從空白頁面新打開一個地址,仍是點擊一個連接打開新頁面,頁面首屏的顯示快
慢直接決定了用戶的第一感覺。而在衆多決定首屏顯示時間的因素中,js 帶來的阻塞無疑是
最爲嚴重的。因爲瀏覽器自己的工做原理,在解析 html 文檔時是至上而下的解析,且 js 在調
用前須要預先定義,在 html 中的體現就是不少 js 函數的定義代碼會直接放在頁面頭部的 head
標籤內。例如:ssi 合併 js(含 jquery)、頭部導航、廣告、快搜接口、功能函數、ivy
頭部引用 js 是爲了頁面上的調用可以生效
分析問題:
Js 阻塞頁面分爲兩種狀況:js 阻塞渲染和 js 阻塞其餘請求
for(var i=0;i<10;i++){
document.getElementsByTagName("div")[i].className="style"+i;
}
var divs=document.getElementsByTagName("div");
for(var i=0;i<10;i++){
divs[i].className="style"+i;
}
<script src="http://www.pconline.com.cn/ssi/js/channel/index.html"
type="text/javascript"></script>
<base target="_blank" />
</head>Js 阻塞渲染:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/jsblock.html
在瀏覽器中,js 腳本和 DOM 渲染分別由兩個獨立的模塊來完成,而瀏覽器自己是單線
程工做的,也就是說,瀏覽器在同一時間只能調用其中的一個模塊來工做,而必須暫停另外
一個。這樣的工做原理使得 js 的執行對頁面的渲染形成了阻塞。
圖 5.2.1 瀏覽器不能同時進行 js 執行和 DOM 渲染
事實上,任何地方的 js 都會阻塞頁面,使頁面渲染中止,只是從用戶體驗的角度來講,
首屏內的 js 形成的阻塞會讓用戶感受更加明顯。其次,js 的執行有可能會帶來對頁面的修改,
在不清楚頁面將會做何修改的時候暫停渲染,也是瀏覽器出於效率上的考慮。
圖 5.2.2 Js 的執行一樣會阻塞頁面
Js 阻塞其餘請求:
除了異步的 js 請求,通常的 js 請求不會和其餘請求併發(用 document.write 可讓 js 和
js 併發),換句話說,此時瀏覽器不會發出其餘請求,瀏覽器此時也不會繼續解析 html 代碼,
必須等到 js 接收完畢,並執行完畢,html 的解析纔會繼續
圖 5.2.3 Js 阻塞頁面
問題引伸:
1.js 代碼在 html 中的位置
因爲瀏覽器至上而下解析 html 的工做原理,爲了減小 js 對渲染的影響,應該讓 js 代碼
越靠近頁面的底部越好,可是這樣會帶來兩個問題:
1、將全部 js 都放在頁面的最後,必然會在頁面渲染完成時佔用不少的時間去執行和加
載 js,此時瀏覽器進度條將會呈現一直運轉的狀態,而頁面也將呈現「假死」(沒法響應用戶
的操做)。
2、將全部 js 都放在頁面的最後,一些元素的事件綁定將會被推後,即在加載過程當中,雖然頁面已經完成渲染和顯示,但用戶的操做會無效。
由此看出,如何在 html 中放置 js 代碼,讓 js 可以平穩加載並執行,讓頁面可以漸進的
過渡渲染,將會是另外一個值得探討的問題。
2.頁面的首屏時間
基調網絡的首屏時間定義爲:
據基調介紹,基調是檢測 800x600 範圍內的 8 個點的顯示狀況來斷定首屏是否顯示的,
這是一種相對感性的方法,目的是爲了最大程度的貼近用戶真實的感覺,而實際操做中,這
樣的時間點倒是受到諸多因素影響,沒法用技術的手段去測試或模擬得出準確的結果。例如:
在首屏結束位置插入一張 1x1 大小的 gif 圖片來標記首屏時間,若是頁面解析很快,這張小
圖的請求就會很快發出,而此時首屏的內容有可能還在加載或是渲染中,因此這種方法並不
能反映最真實的狀況。
圖 5.2.4 基調的工具能夠大概肯定首屏時間,但用其餘技術手段並很差肯定
IE 瀏覽器顯示第一屏主頁面的消耗時間。
首屏的定義以 800X600 像素尺寸爲標準。從開始監測開始計時,到 IE 瀏覽器頁面顯
示高度達到 600 像素且此區域有內容顯示以後的時間。問題總結:
Js 在 web 前端應用變得日益重要,愈來愈多的應用構建在 js 上,js 一方面能給網頁帶來
豐富、便捷的體驗,另外一方面也帶來了效率上的問題。
在 js 阻塞頁面渲染上主要有如下兩個方面:
一方面,瀏覽器單線程的工做原理,使得 js 對頁面產生的阻塞在所不免,加上 ie6 這樣
的低效瀏覽器仍然是市場主流,阻塞帶來的問題在現階段顯得更加明顯;另外一方面,通過測
試有望找到合理安排 js 的方式,實現 js 的漸進過渡,最大程度減輕 js 對頁面的阻塞,而且隨
着瀏覽器的不斷升級與用戶硬件設備的不斷提高,也有助於減小 js 帶來的阻塞。
5.3 異步 js 執行時間不肯定
現狀分析:
先說說異步和同步的概念:
異步是相對於同步而言的,同步並非指同時進行活動,而是指協同、配合的活動。我
們說 A 和 B 同步,是指 A 執行到必定程度時要依靠 B 的某個結果,因而停下來,示意 B 運
行;B 執行,再將結果給 A;A 再繼續操做。
所謂同步,就是在發出一個功能調用時,在沒有獲得結果以前,該調用就不返回,同時其它線程也不能
調用這個方法。
摘自網絡
而異步則偏偏相反,即 A 和 B 的活動互不影響、互不制約,在 A 活動時,B 也能夠執
行。
在 web 中的同步異步每每是指客戶端和服務器端通訊的過程,傳統的過程就是同步的:圖 5.3.1 Web 中的同步和異步模型
目前異步 js 主要由如下兩種方式實現:
1. ajax:不能跨域,依賴 xmlhttprequest
2. appendChild(scriptDom):能夠跨域,不阻塞其後的請求
因爲 ajax 在跨域上的缺陷,如今用得較多的是 appendChild (scriptDom)的形式,如 needJs
和 defineJS 中的異步調用都是以 appendChild(scriptDom)做爲核心方法。
爲了最大程度減小 js 的請求對其後請求的阻塞,咱們的網站中正逐步推行「按需加載、
異步請求」的模式來對待外鏈的 js,瞭解異步 js 的執行時間和特色顯得愈發重要。
分析問題:
異步 js 的執行時間在各瀏覽器中有不一樣的表現:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/asynJs.html
asynJs.html 使用 appendChild 方式異步請求 wirte1 和 write2 兩個 js,write1 體積明顯大於
write2,且 write1 先請求。結果,各瀏覽器下的執行順序略有不一樣:
ie、chrome:誰先返回誰先執行(在沒有緩存的狀況下,write2 會比 write1 先返回,故
能夠看到 write2 先執行);
FF:誰先請求誰先執行既然 js 的執行順序會有區別,若是兩個異步請求的 js 有依賴關係則會引發瀏覽器錯誤:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/asynJs2.html (ie、chrome 下 ctrl+F5
會出錯)
圖 5.3.2 ie 下 write3 先請求,後返回,後執行
問題引伸:
1.異步請求
若是發出異步請求的腳本塊 A 自己執行的時間很長,長到異步請求的腳本 B 已經返回還
未執行完,這時瀏覽器是會繼續執行原來的 A,仍是中斷 A,轉爲執行腳本 B 呢?爲此,我
們增長腳本執行的時間:
Eg:http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/asynJsCut/asynJsCut.html
Ie8 頁面顯示(FF、chrome 執行順序一致,時間稍有不一樣):
能夠看到,waitTime1 執行完後執行了 waitTime2,最後才執行 t1。從 dynatrace 的跟蹤也
能夠看到,t1.js 在 script 腳本還未執行完時就已經接收完畢(圖 5.3.3 箭頭所示棕色位置),
而此時並無馬上執行 t1.js,圖 5.3.4 中顯示,t1.js 是在 script 腳本塊執行完後才執行的。所
以,對於異步請求的 js 接收完後,並不會馬上執行,而會排隊待 js 引擎空閒後再執行。這
一點和 setTimeout 的機制有些相似。
waitTime1 完成:500 毫秒
waitTime2 完成:1003 毫秒
js1 執行 1088 毫秒
t1.js 載入完成 從開始到載入完成花費:1089 毫秒圖 5.3.3
圖 5.3.4 異步 js 返回後並無當即執行
2.script 腳本塊、js 定時器
Js 的執行是按腳本塊爲單位一個一個來執行的。在遇到一個腳本塊時,js 引擎會把這個
腳本塊丟到腳本隊列(也叫調用堆棧)中去排隊執行,一旦當前 js 引擎出於空閒狀態,就會
執行腳本隊列裏的腳本塊,先入隊列的腳本塊會先執行。
因爲 js 爲單線程執行,因此每次只能執行一個腳本塊,這時會阻塞其餘異步 js 的執行(如
鼠標單機事件,定時器觸發,或者異步請求完成),這些異步 js 一樣被安排到腳本隊列中等
待 js 引擎的空閒。
setTimeout 並非嚴格意義上的定時器,而是將 js 在指定時間後安排到腳本隊列的一種
方式,而實際執行該 js 的時間則要看腳本隊列中的狀況以及 js 引擎的空閒狀態來決定。另外,
如何將 js 安排到腳本隊列,各個瀏覽器存在差別。看下面的例子:
http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/setTimeoutJsBlock.html
http://zzb.pcauto.com.cn/power/js/jsProblem/asynJs/setTimeoutJsBlock2.html
第一個例子,FF 下 setTimeout 的 b 偶爾會在 c 前執行,ie8 下即便延時增長,b 也沒法在
c 前執行。
第二個例子,在不延時的狀況下,FF 和 ie8 都會將 b 的執行安排到最後。(ie6 和 FF 類
似)
結論:setTimeout 執行的 js 和內嵌的 js 在執行上的順序沒法保證。
問題總結:
由異步 js 執行時間的不肯定,引出的是對 js 異步執行機制的思考。經過以上的分析能夠
初步得出一下結論:
1.異步 js 的執行主要有如下幾類:用戶觸發的事件處理(鼠標點擊、滾動頁面等)、異步
請求的 js、定時器觸發。
2.js 引擎是單線程的
3.異步 js 會進入 js 調用堆棧中排隊執行4.各瀏覽器對於異步 js 安排到堆棧中的處理方式存在區別,表現出來就是在異步請求 js
以及定時器觸發 js 在執行順序上的不一樣
5.4 defineJS 的使用
現狀分析:
爲何要用 defineJS?
客觀因素:
瀏覽器單線程的本質決定了如下兩個問題:
1.js 的請求阻塞其後的請求(瀏覽器不知道 js 會作什麼操做,因此在請求 js 的時候不會
請求其餘文件,說不定此時 js 會有跳轉操做呢。PS:這裏指的 js 請求是<script src=」」>這樣
的標籤形式)
2.js 的執行阻塞頁面的渲染(由單線程本質決定,具體查閱 5.2 的內容)
這樣兩個問題使得 js 成爲了頁面效率的最大殺手,爲了保證首屏的內容可以最快的顯示,
讓用戶獲得最快的響應,排除阻塞因素、延遲 js 成爲了最直觀的方法, defineJS 由此應運而
生。
現實狀況:
SSI 合併 js 的作法雖然將多個 js 的請求合併成了一個,卻在無心識中增長了頁面 js 的冗
餘度。高耦合在使用上帶來了方便,也帶來了冗餘和笨重,加上 js 對瀏覽器的特殊影響,使
得在這個問題的權衡上,SSI 的作法是弊大於利的。
打破 js 冗餘帶來的不利影響,讓 js 的調用更加靈活、準確,也是 defineJS 的另外一使命。
defineJS 能作什麼?
簡單來講
defineJS 能夠延遲 js 的請求。
defineJS 能夠減小 js 的冗餘。
defineJS 能夠將內嵌 js 對外部 js 的依賴關係控制得更加靈活、準確。
分析問題
defineJS 參數詳解:圖 5.4.1 defineJS 的參數說明
defineJS 流程詳解:
圖 5.4.2 代理機制和回調函數是 defineJS 的核心。
第一步:聲明對須要 defineJS 的函數進行初始化參數配置,如:
defineJS(„usejquery,!$,!jQuery,!$.getScript=/www1/js/pc.jquery1.4.js‟);
具體參數配置能夠參照參數詳解。
(ps:對於對象的多級聲明,層級越高放越後,好比:$.getJSON 在聲明時需放在$後,即:
defineJS(「$,$.getJSON=jquery.js」)
)
第二步:格式化參數
這一步會對參數作一下處理:
1.
defineJS(„usejquery,!$,!jQuery,!$.getScript=/www1/js/pc.jquery1.4.js‟);
會在格式化參數步驟中分解成徹底等效的
defineJS("usejquery =/www1/js/pc.jquery1.4.js ");
defineJS("!$=/www1/js/pc.jquery1.4.js ");
defineJS("!jQuery =/www1/js/pc.jquery1.4.js ");
defineJS("!$.getScript =/www1/js/pc.jquery1.4.js ");
2.經過!判斷是異步仍是同步,若是是同步,url 是否符合規則
3.是否須要延時處理
4.函數是否已經加載
第三步:設置代理函數
這裏簡要說下代理機制的原理:
以(„fn=fn.js‟)爲例:
代理機制會定義一個名字一樣爲 fn 的函數(這裏爲了防止混淆,用 fndl 標識,實際上名字
仍是 fn),當第一次調用 fn(arg)時,其實是調用了 fndl(arg),而 fndl 將 arg 保存,在回調
函數 ok()中將 arg 參數推入到剛加載的真實函數 fn 中。事實上,fndl 起到了保存 arg、轉告
arg 的做用。打個比方:這裏有 boss、小祕、打工仔三我的,boss 有些材料要叫打工仔去買,可是打工
仔外出了,因而 boss 找來小祕,讓小祕等打工仔回來時轉告他要去購買的材料,並交了一
份購買清單(arg 參數)給小祕,boss 就去旅遊了,小祕打了個電話急招打工仔回來,等了
一會,打工仔回來了,小祕第一時間將 boss 交待的事轉告給了打工仔,打工仔接到了清單,
立馬出發,不到一會就將全部材料買回。
這裏的小祕就是打工仔的代理函數,她起到了保存清單和轉交清單的做用。
第四步:加載腳本
具體看下一小節。
第五步:加載完畢,觸發 ok()
當 js 加載完畢後,新的 fn 函數會覆蓋掉代理 fn,並經過 ok 把原參數推到新的 fn 函數中調
用。
defineJS 同步和異步如何選擇:
在 defineJS 的參數中,若是函數名前加上了「!」號,表明此函數調用時,請求 js 用同步
模式,這樣作會使得頁面的解析和渲染、甚至其後的請求都收到阻塞。那爲何還要在
defineJS 中提供同步模式呢?
緣由在於異步模式發出 js 請求後,主程序會繼續執行,此時請求的 js 還未返回,若是主
程序有馬上調用依賴外部js的函數時,就會由於未定義而報錯。具體能夠看下面例子中的yb2:
eg.http://zzb.pcauto.com.cn/power/js/jsProblem/defineJS/defineJSty.html
而同步模式用到了 ajax 的同步機制,當請求發出後,主程序會掛起等待 js 的返回,此時
主程序不會繼續執行,避免了其後調用還未定義的函數。待 js 返回後纔會繼續執行主程序。
能夠看上面的例子中的 tb 調用。
所以,若是行內腳本中存在依賴外部腳本返回值的代碼,就須要用同步模式。
例如:jquery 的鏈式調用:
又如:new 對象的操做:
$(「#id」).hide();//在調用$時,會觸發 jquery 的請求,若是此時不用同步模
式暫停主程序,程序會繼續執行 hide 函數,而 hide 函數在 jquery 請求返回以前是未
定義的,因此會報錯。固然,若是對於 jquery 這類調用必定要使用異步模式也是有辦法的:
1.這須要在請求的 js 中加入一個「外殼函數」,在外殼函數中調用 js 所提供的方法
以 jquery 爲例:
外殼函數:
2.defineJS 的申明函數換成這個外殼函數
3.實際調用時的代碼也需放到外殼函數中
能夠查看 eg.http://zzb.pcauto.com.cn/power/js/jsProblem/defineJS/defineJSty.html
中的 yb3
問題引伸
defineJS 存在的弊端:
1.對於同步模式請求的 js 需使用相對路徑,一些動態應用使用 defineJS 須要在動態本地
存放 js,而不能整站公用。
2.defineJS 需在頭部預先定義,且嵌入到 html 頁面上,若是後期須要更新維護,須要逐
個修改頁面。
3.defineJS 同步模式因爲使用 xmlhttprequest 發出請求,而 xmlhttprequest 對於不能指定文
檔格式的文件,如:js,默認使用 utf-8 編碼。而咱們的 js 通常都使用 gb2312 格式,且中文
在 utf-8 和 gb2312 中編碼不一樣,(英文和特殊字符相同),因此,同步加載的 js 中不該含有中
文,若是必定要加入中文,則需將 js 轉爲 utf-8 編碼。這也是 pc 的 jquery 中含有下面這句話
的緣由:
4.延遲加載的時間沒法精確控制。defineJS 請求的時機是由瀏覽器的解析決定的,當解析
usejquery(function($){
$(xx).xxx;
})
defineJS(「usejquery=jquery.js」);
function usejquery(fn){
fn(jQuery);
}
defineJS(「slidebox=slide.js」);
var a=new slidebox();//異步調用時,執行到 slidebox()時會發出請求,此時
js 還未返回,a 的值已經不是指望獲得的對象了
a.slide();//調用 a 的 slide 方法,而 a 並未成爲 slidebox 的對象,slide 方法
不存在,報錯」速度很快,請求有可能會在某些請求前發出,這主要是因爲瀏覽器的解析和渲染沒法絕對一
致所致使。當瀏覽器解析完首屏的代碼時,每每首屏還未渲染完畢。
defineJS 語句分析:
1.
function(fn){
if(fn)return callback(false);
}//fn 是否認義,fn 需做爲參數,若是未定義,參數會爲 undefined,而在函數外若是未定
義,會報錯
2.scripts[src]={
onload:false,
callbacks:[]
}//把地址做爲子對象名
3.if(arr.push(a)==1)//數組 push 後返回數組長度
4.初始值、初始表達式
var a=b||"default";
var a=b||(b="default");//b 的值會做爲表達式的值返回給 a
5.function alert(s){
window.defineJSlog+=」\n」+s;
}//記錄調試信息,不干擾用戶,可在地址欄用 javascript:alert(defineJSlog)查看
6.fn.toString().indexOf(「defineJSlog」)//查找 fn 的定義代碼中是否含有「defineJSlog」字樣
7.eval.call(window,」fn」)//指定在 window 做用域下執行 fn,在 js 中 eval 和 with 能夠動態
修改做用域
問題總結:
defineJS 是結合了無阻塞和按需加載兩種優化思路的 js 加載方案,雖然還存在同步、預
先定義、相對路徑等問題,但在實際的使用過程當中,defineJS 確實發揮了不錯的效果,使得
頁面在 js 加載上可以更加合理和規範,這也是對前端性能以及資源利用的一次大膽嘗試,而
其中涉及到的不少方法和思路,也是瞭解 javascript 底層原理的很好的實例。5.5 頁面內容的浪費加載
現狀分析
隨着互聯網的高速發展和全民上網環境的不斷提速,web 網站的數量和複雜程度都呈現
出爆炸式增加的趨勢。咱們在愈來愈多的網站上看到,Web 頁面承載着難以置信的信息量。
在門戶網站首頁,多達4、五屏的頁面已經習覺得常,滾動滾輪彷佛成了打開每一個頁面
都必作的操做。而這僅僅是可見的信息量,愈來愈多的頁面將信息隱藏、堆疊起來,即便你
看不到它,即便你不須要它,可是它的確在那裏,它的確從服務器傳輸到了客戶端,更重要
的是,它的確佔用了帶寬,換句話說,它是有成本的。
如何在不影響用戶的操做前提下,最大化的節約消耗和成本,已經成爲另外一個值得研究
的前端領域。
問題分析:
何爲浪費加載:
首先,目前的 web 頁面都基於瀏覽器,而瀏覽器可視區域的大小受到顯示設備的限制,
因此,超出視窗的內容只有經過滾動條纔可以看獲得。換句話說,若是一個頁面有五屏,而
你的用戶在開完第一屏後就關閉了瀏覽器,那麼,第二屏到第五屏的數據就屬於浪費加載的
範疇。
其次,如今的頁面有不少隱藏的內容,默認狀況下是不顯示出來的,除非用戶進行了某
些操做,好比點擊或移上某個元素,纔會觸發顯示。一樣,若是用戶並未進行這樣的操做,
這些內容也屬於浪費加載的範疇。
圖 5.5.1 用戶未切換的選項卡屬於浪費加載的範疇
原則「可見的纔是須要的」
面對頁面上的衆多信息,最大化信息利用率的原則就是「可見的纔是須要的」,換句話說,
當前不可見的就不須要加載、不須要顯示,這也是近來說起比較多的「按需加載」。從傳統的
將信息所有推送給用戶,到讓用戶本身選擇須要的信息,「按需加載」事實上是把更多的權利
交給了用戶。不過,按需加載也是有利有弊的:
利:
1. 減小頁面的浪費加載,節省沒必要要的帶寬
2. 加快頁面的載入時間,減小用戶的等待
弊:
1. 按需加載的內容基本喪失了搜索引擎抓取的可能,會帶來必定流量的減小
2. 按需加載在觸發時才加載數據,用戶會感受有必定延遲
3. 若是沒有合理的選擇按需加載觸發的時機,每每會帶來很差的體驗
有誰在使用「按需加載」
1.yahoo
做爲世界級的門戶網站,yahoo 對於前端的關注在業內是衆所周知的,YUI 也一直是前
端行業的風向標。Yahoo 首頁對於按需加載的使用可謂發揮到了極致,總的頁面只有一屏半
的高度,龐大的各種內容都放在了左側的導航上進行按需加載。這樣的處理,使得 yahoo 首
頁的加載速度極快,左側還提供了定製功能,方便用戶定製感興趣的信息。
圖 5.5.2 Yahoo 首頁左側大量用了「按需加載」
加載方式:發出請求加載,appendChild 插入
2.淘寶
淘寶不只是國內最成功的 C2C 平臺,其在前端領域的實力也在國內名列前茅。
淘寶對於按需加載的使用主要在首頁切換卡和產品列表頁上:
首頁的切換卡中的內容用<textarea>包住,這樣能夠避免瀏覽器解析其中的代碼;圖 5.5.3 淘寶首頁選項卡內容用了 textarea
而產品列表頁則沒有給視窗外的圖片設置 src值,而是把圖片的地址寫在 data-lazyload-src
上,避免請求圖片。
圖 5.5.4 列表頁圖片用 data-lazyload-src 代替 src
3.QQ
首頁採起的是和 yahoo 相同的形式,即異步請求加載,不一樣的是,qq 請求返回的是一個
html 頁面,而 yahoo 則是返回一個 json 數據。從這點來看,qq 的方式更加直觀,便於維護。
而在圖片方面,qq 的作法和淘寶相似,也是用自定義屬性代替圖片 src 的方法:
圖 5.5.3 qq 圖片延遲方式
4.sina
做爲國內門戶三強之一,sina 的信息量可謂巨大,不過實際發現其並未在首頁使用按需
加載,而發如今博客的文章頁中對圖片進行了按需加載的處理:
Sina 的作法是給全部圖片默認設置了 src 爲一張 1x1 的小圖,真實的地址則用自定義屬
性,究其這樣作的緣由,應該是博客內頁的圖片大多由網友上傳,大小不一,先用小圖佔大
圖的位,這樣作可讓按需加載的圖片,在加載完成時不改變文章內容的佈局,避免給用戶
帶來不便。而前面提到的淘寶和 sina 的圖片都是尺寸統一固定的,用樣式進行統一控制就可
以了。
圖 5.5.4 sina 博客文章頁圖片延遲(圖片有設寬高)
按需加載的分類
按加載的內容來分能夠分爲兩類:
1.代碼
按需加載代碼包括 html 的全部代碼,在未加載以前,html 代碼不會被解析,其中的圖片
也不會請求,主要有如下幾種實現方式:textarea、異步請求、iframe
①<textarea>:用 textarea 標籤包住須要按需加載的 html 代碼,當加載事件觸發時,就
將 html 的代碼傳給指定元素的 innerHTML 便可,此時,瀏覽器纔會解析這一段 html 代碼,
若是這段 html 中含有圖片,此時纔會發出圖片請求。優勢:操做簡單,容易維護,響應迅速
缺點:textarea 須要綁定在頁面上,需改動原頁面代碼,textarea 中的內容不會被搜索引
擎抓取到。
②異步請求:此方式是指當觸發事件後,經過 httprequest 或者 DOMScript 請求數據,服
務器返回 json 格式或者 html 片斷,再利用回調函數更新頁面。Json 格式的數據小巧靈活,可
根據頁面的需求生成不一樣的 html,適合各類動態接口使用;html 片斷的方式看起來更加直觀,
便於理解,也免去了在客戶端從新組裝 html 的過程,適合代碼量較大、形式較爲固定的狀況。
優勢:可在多個頁面使用,加載的內容和頁面分離,易於管理
缺點:請求返回會花費必定時間,沒法作到當即刷新內容
③iframe:須要加載的內容放在另外一個頁面中,觸發事件時,用 js 動態插入 iframe,
此時,瀏覽器纔開始請求 iframe 中的內容,以此達到按需加載。
優勢:iframe 的頁面相對獨立,主頁面不受 iframe 的樣式影響。
缺點:會受到 iframe 的各類限制
2.圖片
圖片是網頁中的重要組成部分,其佔用的帶寬相對於文本也要大得多,因此在節約帶寬
上,圖片按需加載每每起到更爲重要的做用。
針對圖片的按需加載主要應用在圖片列表頁和文章頁中,主要的方式是用自定義屬性來
保存真實的圖片地址,當須要加載時纔將圖片地址賦值給 src 屬性。這樣作的原理是圖片只
有當 src 有值時,瀏覽器纔會發出圖片請求。
問題引伸:
事實上,按需加載的難點並不在技術,其難點在於選擇使用的時機。何時該使用按
需加載,何時改用什麼方式,這纔是最難的。想要節約帶寬,前提是不能下降用戶體驗
或者減小訪問量。「天下沒有白吃的午飯」正是這個道理。
從用戶體驗上來講,按需加載是基於 web 的人機交互的一種新的方式,它的核心仍是人。
一切從人的角度出發,基於人的感覺,提供最人性化的服務,這將是按需加載帶給咱們的新
的思考。這樣人性化的操做還表如今圖片瀏覽的預加載上:預先加載後一頁的圖片內容,減
少用戶翻頁時等待的時間,這也是人性化優化的另外一典範。
問題總結:
按需加載在必定程度上能夠節約帶寬,並減小頁面內容的浪費,若是使用得當,還能夠
減小用戶等待時間,提升用戶體驗。可是對於 SEO 要求比較高的頁面,按需加載就顯得力不
從心了;另外這樣的作法須要對頁面的代碼作響應的調整,若是大量使用還需考慮推廣實施
的問題,好比用置標將 img 的 src 屬性自動替換等等,儘可能減小對正常代碼的修改。5.6 Js 外鏈與內嵌的選擇
現狀分析:
Javascript 在 html 中的存在形式有兩種:一是<script>xxxx</script>腳本塊,二是<script
src=」xx.js」></script>外鏈 js 文件,也便是一般說的內嵌和外鏈兩種形式。
查看各個知名站點的源代碼能夠發現,這兩種形式都經常出現,他們散亂的位於各類 html
標籤中間,讓本來優雅的 html 代碼多了幾分「瑕疵」。的確,因爲 script 的特殊性,使得它
在 html 中總會是最爲顯眼的,而 script 在頁面中的位置和存在方式也是值得關注和重視的地
方。
問題分析:
Script 的存在方式
內嵌:
用<script>和</script>包裹住的 javascript 代碼塊。當瀏覽器解析到這樣的代碼時,會以
script 塊爲單位,將其扔給相應的 js 引擎去執行,並暫停等待 js 引擎的結果,待 js 引擎執行
完畢後,才繼續後續的 html 的解析。
外鏈:
在 script 標籤中,用 src 指定請求的外鏈的 js 地址。當瀏覽器解析到這樣的代碼時,會
請求指定的 js,此時瀏覽器暫停繼續解析頁面,待 js 返回後,開始執行請求的 js,在執行後
才繼續頁面 html 的解析。
此外,外鏈 js 也能夠經過異步請求的方式加載,此時的執行順序在不一樣瀏覽器中會存在
差別,具體能夠查閱《異步 js 執行時間不肯定》一節
內嵌與外鏈的多項對比
維護:
內嵌的 js 因爲是放在 html 頁面上的,因此對於內嵌 js 的維護須要涉及到響應的頁面,
在大批量進行 js 修改時,內嵌 js 會很不方便;外鏈 js 因爲從 html 頁面獨立出來,內容只有
一份,相應的維護也只須要修改一次便可。
效率:
外鏈 js 因爲須要瀏覽器請求,並等待服務器返回 js 文件(正常 js 的請求還會阻塞其餘請
求),因此,外鏈 js 的實際使用效率要比內嵌 js 差一些。
緩存:內嵌 js 的體積是直接算在 html 頁面上的,且通常的 html 不會作緩存的操做,也就至關
於內嵌 js 是沒有緩存的;而外鏈 js 則能夠根據其使用特色,進行緩存的設置,以此減小流量
的消耗。
執行順序:
在執行順序上,內嵌的 js 在各瀏覽器中能夠保證徹底按照在 html 中出現的順序來執行;
而外鏈的 js 若是是異步請求,則沒法在全部瀏覽器中都保證執行順序。在以前的《異步 js 執
行時間不肯定》一節中有說道,這裏看一下另一種外鏈 js 執行順序出問題的例子。
Eg: http://zzb.pcauto.com.cn/power/js/jsProblem/out/script2.html
FF 下的結果爲:
第一行
第二行
Ie 下的結果爲:
第二行
第一行
目前分析是因爲 ie 在執行一段 js 的過程當中是不可以中途中斷去請求其餘 js 的,只有等
到執行完了或者說 js 引擎空閒了,js 的請求才會發出;而 ff 一旦有 js 請求,就會中斷 js 執
行。(遊戲網頭部的廣告也是這種狀況)
問題引伸:
事實上,js 的外鏈和內嵌的選擇和 css 的外鏈和內嵌的選擇有類似的地方,好比維護成
本、效率、緩存方面的考慮,不太同樣的是, 外鏈的 css 並不是阻塞元素,因此通常不會對 css
作異步請求的處理,也就不會出如今解析順序上不一致的問題。此外,css 自己就有優先級的
控制機制,例如 id 優於 class 等,使得 css 的解析順序可以獲得很好的控制。
問題總結:
Js 的內嵌和外鏈沒有絕對的優劣,如何選擇須要按照實際狀況來分析,並綜合考慮維護、
效率、緩存、執行順序等多方面因素。javascript