作者:斯迪
鏈接:https://www.zhihu.com/question/21658448/answer/18903129
來源:知乎
著作權(quán)歸作者所有愈涩。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處加矛。
前端是龐大的履婉,包括 HTML、 CSS斟览、 Javascript毁腿、Image 、Flash等等各種各樣的資源苛茂。前端優(yōu)化是復(fù)雜的已烤,針對方方面面的資源都有不同的方式。那么妓羊,前端優(yōu)化的目的是什么 ?
1. 從用戶角度而言胯究,優(yōu)化能夠讓頁面加載得更快、對用戶的操作響應(yīng)得更及時(shí)躁绸,能夠給用戶提供更為友好的體驗(yàn)裕循。
2. 從服務(wù)商角度而言,優(yōu)化能夠減少頁面請求數(shù)净刮、或者減小請求所占帶寬剥哑,能夠節(jié)省可觀的資源。
總之淹父,恰當(dāng)?shù)膬?yōu)化不僅能夠改善站點(diǎn)的用戶體驗(yàn)并且能夠節(jié)省相當(dāng)?shù)馁Y源利用株婴。
前端優(yōu)化的途徑有很多,按粒度大致可以分為兩類暑认,第一類是頁面級別的優(yōu)化督暂,例如 HTTP請求數(shù)、腳本的無阻塞加載穷吮、內(nèi)聯(lián)腳本的位置優(yōu)化等 ;第二類則是代碼級別的優(yōu)化逻翁,例如 Javascript中的DOM 操作優(yōu)化、CSS選擇符優(yōu)化捡鱼、圖片優(yōu)化以及 HTML結(jié)構(gòu)優(yōu)化等等八回。另外,本著提高投入產(chǎn)出比的目的,后文提到的各種優(yōu)化策略大致按照投入產(chǎn)出比從大到小的順序排列缠诅。
一溶浴、頁面級優(yōu)化
1. 減少 HTTP請求數(shù)
這條策略基本上所有前端人都知道,而且也是最重要最有效的管引。都說要減少 HTTP請求士败,那請求多了到底會(huì)怎么樣呢 ?首先,每個(gè)請求都是有成本的褥伴,既包含時(shí)間成本也包含資源成本谅将。一個(gè)完整的請求都需要經(jīng)過 DNS尋址、與服務(wù)器建立連接重慢、發(fā)送數(shù)據(jù)饥臂、等待服務(wù)器響應(yīng)、接收數(shù)據(jù)這樣一個(gè) “漫長” 而復(fù)雜的過程似踱。時(shí)間成本就是用戶需要看到或者 “感受” 到這個(gè)資源是必須要等待這個(gè)過程結(jié)束的隅熙,資源上由于每個(gè)請求都需要攜帶數(shù)據(jù),因此每個(gè)請求都需要占用帶寬核芽。另外囚戚,由于瀏覽器進(jìn)行并發(fā)請求的請求數(shù)是有上限的 (具體參見此處 ),因此請求數(shù)多了以后轧简,瀏覽器需要分批進(jìn)行請求驰坊,因此會(huì)增加用戶的等待時(shí)間,會(huì)給用戶造成站點(diǎn)速度慢這樣一個(gè)印象吉懊,即使可能用戶能看到的第一屏的資源都已經(jīng)請求完了,但是瀏覽器的進(jìn)度條會(huì)一直存在假勿。
減少 HTTP請求數(shù)的主要途徑包括:
(1). 從設(shè)計(jì)實(shí)現(xiàn)層面簡化頁面
如果你的頁面像百度首頁一樣簡單借嗽,那么接下來的規(guī)則基本上都用不著了。保持頁面簡潔转培、減少資源的使用時(shí)最直接的恶导。如果不是這樣,你的頁面需要華麗的皮膚浸须,則繼續(xù)閱讀下面的內(nèi)容惨寿。
(2). 合理設(shè)置 HTTP緩存
緩存的力量是強(qiáng)大的,恰當(dāng)?shù)木彺嬖O(shè)置可以大大的減少 HTTP請求删窒。以有啊首頁為例裂垦,當(dāng)瀏覽器沒有緩存的時(shí)候訪問一共會(huì)發(fā)出 78個(gè)請求,共 600多 K數(shù)據(jù) (如圖 1.1)肌索,而當(dāng)?shù)诙卧L問即瀏覽器已緩存之后訪問則僅有 10個(gè)請求蕉拢,共 20多 K數(shù)據(jù) (如圖 1.2)。 (這里需要說明的是,如果直接 F5刷新頁面的話效果是不一樣的晕换,這種情況下請求數(shù)還是一樣午乓,不過被緩存資源的請求服務(wù)器是 304響應(yīng),只有 Header沒有Body 闸准,可以節(jié)省帶寬 )
怎樣才算合理設(shè)置 ?原則很簡單益愈,能緩存越多越好,能緩存越久越好夷家。例如蒸其,很少變化的圖片資源可以直接通過 HTTP Header中的Expires設(shè)置一個(gè)很長的過期頭 ;變化不頻繁而又可能會(huì)變的資源可以使用 Last-Modifed來做請求驗(yàn)證。盡可能的讓資源能夠在緩存中待得更久瘾英。關(guān)于 HTTP緩存的具體設(shè)置和原理此處就不再詳述了枣接,有興趣的可以參考下列文章:
HTTP1.1協(xié)議中關(guān)于緩存策略的描述
Fiddler HTTP Performance中關(guān)于緩存的介紹
(3). 資源合并與壓縮
如果可以的話,盡可能的將外部的腳本缺谴、樣式進(jìn)行合并但惶,多個(gè)合為一個(gè)。另外湿蛔, CSS膀曾、 Javascript、Image 都可以用相應(yīng)的工具進(jìn)行壓縮阳啥,壓縮后往往能省下不少空間添谊。
(4). CSS Sprites
合并 CSS圖片,減少請求數(shù)的又一個(gè)好辦法察迟。
(5). Inline Images
使用 data: URL scheme的方式將圖片嵌入到頁面或 CSS中斩狱,如果不考慮資源管理上的問題的話,不失為一個(gè)好辦法扎瓶。如果是嵌入頁面的話換來的是增大了頁面的體積所踊,而且無法利用瀏覽器緩存。使用在 CSS中的圖片則更為理想一些概荷。
(6). Lazy Load Images(自己對這一塊的內(nèi)容還是不了解)
這條策略實(shí)際上并不一定能減少 HTTP請求數(shù)秕岛,但是卻能在某些條件下或者頁面剛加載時(shí)減少 HTTP請求數(shù)。對于圖片而言误证,在頁面剛加載的時(shí)候可以只加載第一屏继薛,當(dāng)用戶繼續(xù)往后滾屏的時(shí)候才加載后續(xù)的圖片。這樣一來愈捅,假如用戶只對第一屏的內(nèi)容感興趣時(shí)遏考,那剩余的圖片請求就都節(jié)省了。 有啊首頁 曾經(jīng)的做法是在加載的時(shí)候把第一屏之后的圖片地址緩存在 Textarea標(biāo)簽中蓝谨,待用戶往下滾屏的時(shí)候才 “惰性” 加載诈皿。
2. 將外部腳本置底(將腳本內(nèi)容在頁面信息內(nèi)容加載后再加載)
前文有談到林束,瀏覽器是可以并發(fā)請求的,這一特點(diǎn)使得其能夠更快的加載資源稽亏,然而外鏈腳本在加載時(shí)卻會(huì)阻塞其他資源壶冒,例如在腳本加載完成之前,它后面的圖片截歉、樣式以及其他腳本都處于阻塞狀態(tài)胖腾,直到腳本加載完成后才會(huì)開始加載。如果將腳本放在比較靠前的位置瘪松,則會(huì)影響整個(gè)頁面的加載速度從而影響用戶體驗(yàn)咸作。解決這一問題的方法有很多,在 這里有比較詳細(xì)的介紹 (這里是譯文和 更詳細(xì)的例子 )宵睦,而最簡單可依賴的方法就是將腳本盡可能的往后挪记罚,減少對并發(fā)下載的影響。
3. 異步執(zhí)行 inline腳本(其實(shí)原理和上面是一樣壳嚎,保證腳本在頁面內(nèi)容后面加載桐智。)
inline腳本對性能的影響與外部腳本相比,是有過之而無不及烟馅。首頁说庭,與外部腳本一樣, inline腳本在執(zhí)行的時(shí)候一樣會(huì)阻塞并發(fā)請求郑趁,除此之外刊驴,由于瀏覽器在頁面處理方面是單線程的,當(dāng) inline腳本在頁面渲染之前執(zhí)行時(shí)寡润,頁面的渲染工作則會(huì)被推遲捆憎。簡而言之, inline腳本在執(zhí)行的時(shí)候梭纹,頁面處于空白狀態(tài)躲惰。鑒于以上兩點(diǎn)原因,建議將執(zhí)行時(shí)間較長的 inline腳本異步執(zhí)行栗柒,異步的方式有很多種礁扮,例如使用 script元素的defer 屬性(存在兼容性問題和其他一些問題知举,例如不能使用 document.write)瞬沦、使用setTimeout ,此外雇锡,在HTML5中引入了 Web Workers的機(jī)制逛钻,恰恰可以解決此類問題。
4. Lazy Load Javascript(只有在需要加載的時(shí)候加載锰提,在一般情況下并不加載信息內(nèi)容曙痘。)
隨著 Javascript框架的流行芳悲,越來越多的站點(diǎn)也使用起了框架。不過边坤,一個(gè)框架往往包括了很多的功能實(shí)現(xiàn)名扛,這些功能并不是每一個(gè)頁面都需要的,如果下載了不需要的腳本則算得上是一種資源浪費(fèi) -既浪費(fèi)了帶寬又浪費(fèi)了執(zhí)行花費(fèi)的時(shí)間茧痒。目前的做法大概有兩種肮韧,一種是為那些流量特別大的頁面專門定制一個(gè)專用的 mini版框架,另一種則是 Lazy Load旺订。YUI 則使用了第二種方式弄企,在 YUI的實(shí)現(xiàn)中,最初只加載核心模塊区拳,其他模塊可以等到需要使用的時(shí)候才加載拘领。
5. 將 CSS放在 HEAD中
如果將 CSS放在其他地方比如 BODY中,則瀏覽器有可能還未下載和解析到 CSS就已經(jīng)開始渲染頁面了樱调,這就導(dǎo)致頁面由無 CSS狀態(tài)跳轉(zhuǎn)到 CSS狀態(tài)约素,用戶體驗(yàn)比較糟糕。除此之外本涕,有些瀏覽器會(huì)在 CSS下載完成后才開始渲染頁面业汰,如果 CSS放在靠下的位置則會(huì)導(dǎo)致瀏覽器將渲染時(shí)間推遲。
6. 異步請求 Callback(就是將一些行為樣式提取出來菩颖,慢慢的加載信息的內(nèi)容)
在某些頁面中可能存在這樣一種需求样漆,需要使用 script標(biāo)簽來異步的請求數(shù)據(jù)。類似:
Javascript:
/Callback 函數(shù)/
function myCallback(info){
//do something here
}
HTML:
cb返回的內(nèi)容 :
myCallback('Hello world!');
像以上這種方式直接在頁面上寫 <script>對頁面的性能也是有影響的晦闰,即增加了頁面首次加載的負(fù)擔(dān)放祟,推遲了 DOMLoaded和window.onload 事件的觸發(fā)時(shí)機(jī)。如果時(shí)效性允許的話呻右,可以考慮在 DOMLoaded事件觸發(fā)的時(shí)候加載跪妥,或者使用 setTimeout方式來靈活的控制加載的時(shí)機(jī)。
7. 減少不必要的 HTTP跳轉(zhuǎn)
對于以目錄形式訪問的 HTTP鏈接声滥,很多人都會(huì)忽略鏈接最后是否帶 ’/'眉撵,假如你的服務(wù)器對此是區(qū)別對待的話,那么你也需要注意落塑,這其中很可能隱藏了 301跳轉(zhuǎn)纽疟,增加了多余請求。具體參見下圖憾赁,其中第一個(gè)鏈接是以無 ’/'結(jié)尾的方式訪問的污朽,于是服務(wù)器有了一次跳轉(zhuǎn)。
8. 避免重復(fù)的資源請求
這種情況主要是由于疏忽或頁面由多個(gè)模塊拼接而成龙考,然后每個(gè)模塊中請求了同樣的資源時(shí)蟆肆,會(huì)導(dǎo)致資源的重復(fù)請求
二矾睦、代碼級優(yōu)化
1. Javascript
(1). DOM
DOM操作應(yīng)該是腳本中最耗性能的一類操作,例如增加炎功、修改枚冗、刪除 DOM元素或者對 DOM集合進(jìn)行操作。如果腳本中包含了大量的 DOM操作則需要注意以下幾點(diǎn):
a. HTML Collection(HTML收集器蛇损,返回的是一個(gè)數(shù)組內(nèi)容信息)
在腳本中 document.images官紫、document.forms 塑径、getElementsByTagName()返回的都是 HTMLCollection類型的集合讨衣,在平時(shí)使用的時(shí)候大多將它作為數(shù)組來使用,因?yàn)樗?length屬性帜矾,也可以使用索引訪問每一個(gè)元素床玻。不過在訪問性能上則比數(shù)組要差很多毁涉,原因是這個(gè)集合并不是一個(gè)靜態(tài)的結(jié)果,它表示的僅僅是一個(gè)特定的查詢锈死,每次訪問該集合時(shí)都會(huì)重新執(zhí)行這個(gè)查詢從而更新查詢結(jié)果贫堰。所謂的 “訪問集合” 包括讀取集合的 length屬性、訪問集合中的元素待牵。
因此其屏,當(dāng)你需要遍歷 HTML Collection的時(shí)候,盡量將它轉(zhuǎn)為數(shù)組后再訪問缨该,以提高性能偎行。即使不轉(zhuǎn)換為數(shù)組,也請盡可能少的訪問它贰拿,例如在遍歷的時(shí)候可以將 length屬性蛤袒、成員保存到局部變量后再使用局部變量。
b. Reflow & Repaint
除了上面一點(diǎn)之外膨更, DOM操作還需要考慮瀏覽器的 Reflow和Repaint 妙真,因?yàn)檫@些都是需要消耗資源的,具體的可以參加以下文章:
如何減少瀏覽器的repaint和reflow?
Understanding Internet Explorer Rendering Behaviour
<u>Notes on HTML Reflow</u>
(2). 慎用 with
with(obj){ p = 1}; 代碼塊的行為實(shí)際上是修改了代碼塊中的 執(zhí)行環(huán)境 荚守,將obj放在了其作用域鏈的最前端珍德,在 with代碼塊中訪問非局部變量是都是先從 obj上開始查找,如果沒有再依次按作用域鏈向上查找矗漾,因此使用 with相當(dāng)于增加了作用域鏈長度锈候。而每次查找作用域鏈都是要消耗時(shí)間的,過長的作用域鏈會(huì)導(dǎo)致查找性能下降缩功。
因此晴及,除非你能肯定在 with代碼中只訪問 obj中的屬性都办,否則慎用 with嫡锌,替代的可以使用局部變量緩存需要訪問的屬性虑稼。
(3). 避免使用 eval和 Function
每次 eval 或 Function 構(gòu)造函數(shù)作用于字符串表示的源代碼時(shí),腳本引擎都需要將源代碼轉(zhuǎn)換成可執(zhí)行代碼势木。這是很消耗資源的操作 —— 通常比簡單的函數(shù)調(diào)用慢 100倍以上蛛倦。
eval 函數(shù)效率特別低,由于事先無法知曉傳給 eval 的字符串中的內(nèi)容啦桌,eval在其上下文中解釋要處理的代碼溯壶,也就是說編譯器無法優(yōu)化上下文,因此只能有瀏覽器在運(yùn)行時(shí)解釋代碼甫男。這對性能影響很大且改。
Function 構(gòu)造函數(shù)比 eval略好,因?yàn)槭褂么舜a不會(huì)影響周圍代碼 ;但其速度仍很慢板驳。
此外又跛,使用 eval和 Function也不利于Javascript 壓縮工具執(zhí)行壓縮。
(4). 減少作用域鏈查找(這方面設(shè)計(jì)到一些內(nèi)容的相關(guān)問題)
前文談到了作用域鏈查找問題若治,這一點(diǎn)在循環(huán)中是尤其需要注意的問題慨蓝。如果在循環(huán)中需要訪問非本作用域下的變量時(shí)請?jiān)诒闅v之前用局部變量緩存該變量,并在遍歷結(jié)束后再重寫那個(gè)變量端幼,這一點(diǎn)對全局變量尤其重要礼烈,因?yàn)槿肿兞刻幱谧饔糜蜴湹淖铐敹耍L問時(shí)的查找次數(shù)是最多的婆跑。
低效率的寫法:
// 全局變量
var globalVar = 1;
function myCallback(info){
for( var i = 100000; i--;){
//每次訪問 globalVar 都需要查找到作用域鏈最頂端此熬,本例中需要訪問 100000 次
globalVar += i;
}
}
更高效的寫法:
// 全局變量
var globalVar = 1;
function myCallback(info){
//局部變量緩存全局變量
var localVar = globalVar;
for( var i = 100000; i--;){
//訪問局部變量是最快的
localVar += i;
}
//本例中只需要訪問 2次全局變量
在函數(shù)中只需要將 globalVar中內(nèi)容的值賦給localVar 中區(qū)
globalVar = localVar;
}
此外,要減少作用域鏈查找還應(yīng)該減少閉包的使用滑进。
(5). 數(shù)據(jù)訪問
Javascript中的數(shù)據(jù)訪問包括直接量 (字符串摹迷、正則表達(dá)式 )、變量郊供、對象屬性以及數(shù)組峡碉,其中對直接量和局部變量的訪問是最快的,對對象屬性以及數(shù)組的訪問需要更大的開銷驮审。當(dāng)出現(xiàn)以下情況時(shí)鲫寄,建議將數(shù)據(jù)放入局部變量:
a. 對任何對象屬性的訪問超過 1次
b. 對任何數(shù)組成員的訪問次數(shù)超過 1次
另外,還應(yīng)當(dāng)盡可能的減少對對象以及數(shù)組深度查找疯淫。
(6). 字符串拼接
在 Javascript中使用"+" 號(hào)來拼接字符串效率是比較低的地来,因?yàn)槊看芜\(yùn)行都會(huì)開辟新的內(nèi)存并生成新的字符串變量,然后將拼接結(jié)果賦值給新變量熙掺。與之相比更為高效的做法是使用數(shù)組的 join方法未斑,即將需要拼接的字符串放在數(shù)組中最后調(diào)用其 join方法得到結(jié)果。不過由于使用數(shù)組也有一定的開銷币绩,因此當(dāng)需要拼接的字符串較多的時(shí)候可以考慮用此方法蜡秽。
關(guān)于 Javascript優(yōu)化的更詳細(xì)介紹請參考:
Write Efficient Javascript(PPT)
Efficient JavaScript
2. CSS選擇符
在大多數(shù)人的觀念中府阀,都覺得瀏覽器對 CSS選擇符的解析式從左往右進(jìn)行的,例如
toc A { color: #444; }
這樣一個(gè)選擇符芽突,如果是從右往左解析則效率會(huì)很高试浙,因?yàn)榈谝粋€(gè) ID選擇基本上就把查找的范圍限定了,但實(shí)際上瀏覽器對選擇符的解析是從右往左進(jìn)行的寞蚌。如上面的選擇符田巴,瀏覽器必須遍歷查找每一個(gè) A標(biāo)簽的祖先節(jié)點(diǎn),效率并不像之前想象的那樣高挟秤。根據(jù)瀏覽器的這一行為特點(diǎn)壹哺,在寫選擇符的時(shí)候需要注意很多事項(xiàng),有人已經(jīng)一一列舉了艘刚, 詳情參考此處斗躏。
3. HTML
對 HTML本身的優(yōu)化現(xiàn)如今也越來越多的受人關(guān)注了,詳情可以參見這篇 總結(jié)性文章 昔脯。
4. Image壓縮
圖片壓縮是個(gè)技術(shù)活啄糙,不過現(xiàn)如今這方面的工具也非常多,壓縮之后往往能帶來不錯(cuò)的效果云稚,具體的壓縮原理以及方法在《 Even Faster Web Sites》第10 章有很詳細(xì)的介紹隧饼,有興趣的可以去看看。
總結(jié)
本文從頁面級以及代碼級兩個(gè)粒度對前端優(yōu)化的各種方式做了一個(gè)總結(jié)静陈,這些方法基本上都是前端開發(fā)人員在開發(fā)的過程中可以借鑒和實(shí)踐的燕雁,除此之外,完整的前端優(yōu)化還應(yīng)該包括很多其他的途徑鲸拥,例如 CDN拐格、 Gzip、多域名刑赶、無 Cookie服務(wù)器等等捏浊,由于對于開發(fā)人員的可操作性并不強(qiáng)大,在此也就不多敘述了撞叨,詳細(xì)的可以參考 Yahoo和Google 的這些“金科玉律