摘要:前端性能優化的涉及點從服務器到協議再到宿主環境本身都要有比較深刻的認識,業界目前主要還是以雅虎總結出來條前端性能優化的黃金軍規為參考。
歡迎大家前往騰訊云技術社區,獲取更多騰訊海量技術實踐干貨哦~
導語 : 從事前端有6年+的時間了,從最開始的美工到重構再到偏向js邏輯開發的前端開發,一直在前端這個行業里面摸索和學習,我現在將自己這些年的一個心得體會來個系統性的梳理寫成一篇關于性能優化的主題文章,希望對大家有點幫助,也歡迎大家提出各種意見和建議。
作者:劉勇剛
前端工程師是一個最近這5-6年才開始慢慢被互聯網公司重視起來的一個職業,可以說是一個新興行業,我用一張簡單的思維導圖帶大家回顧一下前端技術發展的歷程以及未來一個展望:
1.0時代沒什么說的,html、css打天下的時代,那個時候你會用js開發個計算器就牛逼到不行。2.0時代是最好的時代,新技術、新思想蓬勃發展,堪稱前端的工業革命,前端人員的地位得到了充分認可,門檻也有一定的提升。前端性能優化的涉及點從服務器到協議再到宿主環境本身都要有比較深刻的認識,業界目前主要還是以雅虎總結出來35條前端性能優化的黃金軍規(http://www.cnblogs.com/siqi/p...) 為參考。今天我想將這些年對前端的性能優化的經驗思考整體來個串燒,帶大家鳥瞰一下前端性能優化目前的一些通行做法以及這么做的出發點。文章初衷主要是對一些性能優化基礎知識回顧和體系梳理,不對具體技術點做深入分析,點到為止,個人理解不對的地方歡迎各位大神拍磚,拋磚引玉。
引入話題前我還是先從一個老生常談的話題開始:
“從用戶輸入URl到頁面展示給用戶瀏覽器客戶端的過程中發生了什么?”
這里用個圖表簡單描述一下幾個步驟:
web優化的目標就是如何讓用戶更快、更簡單易用、更流暢的使用我們的服務,對于前端開發而言就是如何讓我們的資源體量更小、數量更精簡、內容更早呈現、交互更加人性化。
web性能優化有個大家比較公認的二八原則,就是資源從服務器處理完下發到客戶端的瀏覽器上(上圖第6步)所占的時間比例大概是整個過程的20%,也就是說服務器端可以優化的空間的效率提升并不會很明顯,前端性能優化成為web性能優化重點考慮的領域,我下面將會從以下幾個維度去做了自己的一個思考(跟35條軍規有一定重疊)和總結:
一、瀏覽器宿主環境1、突破單線程解析渲染阻塞限制
瀏覽器是一個單線程解析模式去解析渲染從服務器端拿到的html文本,css加載的過程中會對后續的腳本資源加載造成阻塞,腳本的加載也會阻塞后續DOM結構的解析造成頁面的留白時間增長,雅虎的35條軍規中有一條就是樣式文件放在頭部,腳本文件放在DOM節點最末尾,減少阻塞。這里還有幾個針對腳本文件的優化:
針對不需要DOM操作(主要考慮是需要操作DOM的腳本往往需要獲取一些樣式信息)的Js腳本可以采用動態創建script的方式載入,動態載入的腳本不阻塞后續資源的加載。
腳本文件加載可以加上defer或者async屬性標識防止阻塞(關于兩者區別可以參考)
2、利用事件冒泡特性
瀏覽器的事件模型的冒泡的特性(瀏覽器事件模型不清楚的自行搜索了解)我覺得是最牛逼的設計之一,解決了瀏覽器因為解析DOM模型不同步導致開發者往DOM對象注冊事件回調找不到對象的問題。
瀏覽器事件注冊有3個級別定義,DOM 0級事件注冊(利用DOM元素行內事件屬性onclick注冊事件回調),DOM 1級事件注冊(利用DOM元素對象的onclick API 在外部注冊事件回調),DOM 2級事件注冊(利用利用DOM元素對象的addEventListner/attachEvent API 在外部注冊事件回調)。這里性能優化的建議就是利用DOM2級在目標DOM的父標簽(大部分框架是在body標簽統一注冊事件監聽)注冊回調,收攏事件監聽入口同時節約了DOM節點引用開銷。
3、避開Cookie性能bug
Cookie是前端作為前后臺登錄態校驗最通常用的緩存方案,但鑒于瀏覽器在每次都會往同域的任何資源的http請求中自動帶上cookie信息的情況,這里有必要進行優化一下,因為像css、js、image這些資源請求是不需要cookie信息的,會無端造成請求帶寬的浪費(想象一下我們的cookie大小假設為10K,100個請求就是近1M的大小,高并發下以我們現行網絡帶寬也是蠻大的一筆負擔了)。Cookie free性能優化方案的處理方式是CDN異域靜態資源服務器部署我們的前端css、js、image資源。
以自己目前負責的香港跨境匯款為例
頁面路徑下的資源的請求:
CDN資源加載的請求:
通過對比CDN分開部署的資源請求并沒有帶上cookie信息。
4、突破瀏覽器并發連接限制
瀏覽器針對domain,而非頁面page做并發連接限制的特性,domain hash的技術優化方案的處理方式是將資源劃分域分開部署,但因為過多的域劃分會增加多余的DNS開銷,這里通行的數量是3個以內。目前我們的港菲匯款業務只有兩個域名分開部署,一個主站,一個CDN,我個人建議可以將CDN中的圖片資源再多帶帶再分一個域名部署會更好些,為什么多帶帶把圖片抽出來,后面會講到。
5、利用GPU硬件加速瀏覽器渲染
針對一些界面渲染過程比較耗時的情況下,可以利用CSS3屬性開啟GPU來加速渲染我們的DOM,開啟很簡單一般我是用-webkit-transform:translateZ(0)假3D屬性來喚起系統GPU加速渲染功能,關于為什么會這樣,我這里做個簡單的解釋:
對于我們的瀏覽器而言,拿到我們的html文本串開始按順序解析成DOM樹,并與同步解析出來的CSS匹配生成渲染樹(跟DOM樹的節點不是一一對應,比如display:none的節點就不會插入渲染樹)
圖片來源:https://segmentfault.com/a/11...
瀏覽器將渲染樹的節點用一個圖層表示,這樣層層疊加在一起生成layout,有點像ps的圖層疊加的概念(可以通過火狐瀏覽器開發者工具3維展示更直觀),一般情況下對節點的任何涉及尺寸的改變都會引起layout的重排重繪(重排和重繪是造成瀏覽器渲染的最大性能損耗的因素),但有種開小灶的情況Composite Layers(復合圖層)直接交給我們GPU中多帶帶的合成器進程處理,自身變化不會引起其他層的位置變化,不會引起重排重繪。tranform 3d屬性是可以悄悄的告訴我們的瀏覽器把元素解析作為復合圖層交給多帶帶進程去處理的。
注:這里有個原則,不能濫用我們的加速,因為過多開啟硬件加速會消耗更多的用戶內存空間,也會比較耗電,一般針對css3動畫建議開啟
二、Http維度1、減少http請求數量
a、通行解決方案
css、script合并:gulp、webpack都能夠很簡單的通過任務腳本的方式去自動化解決,目前我們團隊是用我們自研的前端構建工具配合我們的Dust庫做的發布前的資源打包任務,核心就是用的gulp。
css sprites雪碧圖:將網站常用的一些小圖片整合到一張大圖上來,樣式里面通過background-position二維坐標定位找到自己的圖片。這里有個原則,一般是將網站復用率較高的,不太容易變動的圖標和圖片,比如按鈕、平鋪背景小圖片等。
font-icon字體圖標:字體圖標庫的使用,是一個非常有創新的方式,因為是矢量的,解決位圖像素放大變虛的問題,體驗很好,相比同樣矢量的SVG來說使用更簡單,一個css的font-family就可以像平時設置字體一樣使用,淘寶是國內這方面的先行者,有自己的一套很開放的矢量圖標庫平臺。淘寶自身的許多小圖標都是用的字體圖標來展示的。
圖片base64編碼傳輸:圖片base64編碼后,可以讓瀏覽器減少自身的一次http請求,但因為自身的一些缺陷,不能濫用(即使一個很小的圖片編碼后都會有一大串字符,增加了我們CSS體積,性能不降反升),我的建議是針對那些全站通用或者體積很小不好整合到雪碧圖里面的圖標進行編碼,當然還有很多不同的場景大家自己權衡。
圖片延時加載:主要是為了減少首屏一次性圖片的加載量。具體做法是給圖片或者標簽設置一個私有行內屬性data-image(當然可以自己隨便定義)存放目標圖片地址信息,監聽瀏覽器的滾動事件,標簽到了瀏覽器可視區域就將圖片地址放入圖片的src屬性中或者作為標簽的樣式的背景圖片中展示。淘寶首頁的做法是用一個div來做延時圖片加載,通過背景圖片來展示最后的圖片。
圖片展示前:
圖片展示后:
b、緩存機制
協議緩存方案:利用http緩存協議頭cache-control做304緩存,或者更精確的ETAG設置依據資源的修改時間來設置緩存方案。但目前更有效或者極端的做法是利用max-expire-time,設置資源的最大緩存時間假設為1年的長緩存,更新采用非覆蓋式更新的方式是目前大公司通行的做法。這樣每次資源請求的時候都是只從客戶端緩存讀取(status:200,size:from
cache),而不是還要跑一次http請求到服務器端拿到304狀態。還是以一張淘寶首頁圖片長緩存的截圖為例:
appCache應用緩存方案:離線應用緩存是h5提供一個比較有效的離線應用方案,利用navigator.online
、window.applicationCache對象、服務器.appcache(以前是.manifest)配置文件保證在脫機下的移動web應用照常能用,如果要做數據的離線還要加上window.localStorage做離線數據的保存。這里簡單說一下接入離線應用需要的幾個步驟:
1、給需要做離線緩存的頁面html標簽設定manifest屬性,指定緩存的配置文件 cache.appcahe(可以設定任何擴展名,只要在服務器端配置mime-type為text/cache-manifest就行)。
2、創建上一步指定的cache.appcache配置文件,按以下截圖說明來配置資源
3、在服務器端配置配置文件的擴展名映射的mime-type為text/cache-manifest
appcache離線方案詬病太多,目前接入的不多,有種慢慢變棄兒的趨勢,這里提出來讓大家權衡
**PWA(Progressive Web
Apps)方案**:谷歌提出的一套全新的離線web方案,利用manifest.json配置文件、window.serviceWorker對象來實現類原生app體驗的離線應用方案,可以說是瀏覽器應用緩存的一個脫胎升級方案(鑒于文章篇幅,這里不做介紹了)。
2、減輕http數據請求大小
a、通行解決方案
css、script、圖片壓縮:這些可以gulp或者webpack自動化腳本里面定義腳本任務來完成。
服務器開啟gzip壓縮:一般現在服務器都有開啟Gzip壓縮,壓縮率通常都是30%以上,效果還是不錯的。
原圖:
Gzip壓縮后:
圖片服務器動態響應方案:這個方案對應上面宿主環境維度domain hash多帶帶出來一個獨立域名部署圖片資源的方案介紹。圖片資源是網站請求資源中一個非常大頭的開銷,以前大家可以在靜態資源服務器中建個image目錄存放就完事,隨著網站服務發展,圖片不僅面臨多樣化、高并發帶來的壓力,在移動端wap站點中更是要針對不同的分辨率屏幕下圖片尺寸動態適配的場景以為了節省帶寬的需求。圖片服務器的多帶帶架構有一定的復雜度(如果考慮到高并發下的容災、緩存機制的話不亞于一個大型web網站集群的搭建,這里有篇文章推薦大家閱讀),這里只討論一下其中負責切圖服務部分的服務器(簡稱切圖服務器)功能,切圖服務器對外提供一個restful的url調用,比如http://www.xxx.com/xxx圖片路...“xxx圖片路徑”下的xxx圖片等比壓縮成130*120的圖片尺寸并返回,這塊服務可以使用我們前端比較熟悉的node創建,當然也可以用PHP來提供。
b、頁面切片預加載方案
性能優化靜態資源維度最后一塊內容就是針對頁面,如何盡早輸出頁面模塊,減少留白時間是一個思考點。facebook應用的BigPipe方案是個很不錯的借鑒思想,還有淘寶也有首頁做了相應的切片方案,對頁面合理的分塊,在服務器和客戶端建立某種對應機制,讓各個頁面塊并行的在服務器端拼接完成并吐出來,目前我對這塊沒有太深的了解,這里只是提出bigPipe的方案供大家參考。
三、TCP維度TCP連接中的3次握手、慢啟動的一些特性注定了連接通道的利用效率成為制約性能的一個很大的因素。因為http是基于TCP的應用協議,TCP層維度考慮還得從http幾個版本的發展歷史來看:
http1.0時期:tcp連接是基于一種單通道順序等待請求響應方式(客戶端每發一個請求都要重新建立連接),特定歷史背景下產生的,低效率很難跟上時代發展,99年在1.0基礎上修訂出1.1版本,并沿用至今。
http1.1時期:在請求頭信息加入keep Alive保持連接的一定活性(當然也加入了100
Status節約帶寬、cache特性等),允許在一個連接通道生命期內重復發送不同的應用請求,一定程度上減輕了連接資源利用效率問題,但當用戶瀏覽網頁時間大于連接活性周期再次請求的時候仍然要重新建立這些請求,在大型科技公司對高并發高可用性下資源高效利用的背景下,1.1版本還是難滿足大公司對高性能條件下網絡資源的高效率利用的要求。
谷歌(叒是谷歌,牛逼)率先在09年基于TCP開發出全新SPDY應用協議,解決了多路復用請求優化、服務器推送的痛點問題,也為后面http2.0的推出奠定了基礎。
我們可以做的優化:減少一些不必要的請求(掃除404死連接、304請求用我們的長緩存機制)去優化,盡量減少一些不必要的連接請求數。
鑒于js語言本身的靈活性,以及每個人的開發習慣,很難有很好的一個方式去驗證開發者的代碼實現的效率(目前更多的是用打點測速的方式去監控代碼的執行時間),更多的是一種建議,大家有更好的建議可以提出來分享。
單線程限制:利用異步回調&多線程API突破js語言單線程帶來的內存開銷利用不充分的問題,現有可以利用的一些異步方式的回調都可以嘗試利用比如settimeout,setinterval,requestAnimationframe(推薦用它),多線程的API方式有WebWork。這里推薦日本的一個開發者開發的一個多線程前端庫Concurrent.Thread.js(它是作者用setTimeout和setInterval來模擬的多線程,可以自行網上搜索了解)。
重點優化代碼中的循環結構體:就代碼本身而言影響執行效率的大部分是循環體結構和算法設計不合理導致的時間復雜度和空間復雜度的增加。這里放兩段實際項目中的代碼截圖來對比:
實例功能需要:實現輸入框每4個字符一個空格隔開的效果
低效實現方式,用for循環:
改進后的方式:
合理使用設計模式優化代碼結構:設計模式的合理利用(不能濫用)可以起到內存優化提高執行效率效果,比如單例和簡單工廠在創建xhr請求對象中的利用:創建一個簡單工廠向外面提供xhr對象,工廠內部用閉包開辟一個數組隊列單例用來存放xhr對象,當調用者需要xhr對象時工廠就從隊列里取出readyState狀態為空閑(0/4)的xhr對象否則重新創建并放入隊列中。在有大量ajax對象請求的應用下可以最大限度節約創建xhr消耗的內存開銷,這里用個簡圖來描述一下思路:
其他一些優化建議:比如減少js頻繁操作dom節點的次數(這個本來想放在宿主環境維度)減少瀏覽器的重排重繪;比如針對dom標簽對象(主要是針對ie下dom對象的引用會被GC回收遺漏的問題)、閉包內部的引用類型的變量用完過后記得要及時釋放,避免造成內存泄漏。
五、產品交互邏輯性能優化一般都是從技術角度去入手,但我們的目標之一“讓用戶簡單易用”也是性能優化的一環。當技術性能缺陷難于避免的時候,作為前端交互實現的執行者,更應該配合產品和交互設計師提出一個我們認為更好的交互邏輯體驗方案去讓我們的數據加載不那么讓用戶有等待的感知,讓我們的提示更加的人性舒服。(交互設計師更加專業,我這里不敢班門弄斧)
Web3.0時代的前端展望:
文章最后對Web3.0時代做個自己的猜想,web3.0目前在業內還沒有很明確定義,大家可以大膽猜想前端行業未來形態。我在這先YY一下,人工智能、大數據廣泛應用應該會成為推動前端進入3.0的時代的最好契機,以此引發的前端新的革命:
瀏覽器成為一個系統生態(至于哪個瀏覽器現在不好說,現在谷歌瀏覽器PWA方案提供給前端類app開發方案就有這個趨勢,以后都不需要裝系統了)。前端不再是數據的搬運工,在web領域很有可能除了底層維護數據庫的工程師們繼續深耕外(主要切入云計算領域),其它的都可以轉到前端做瀏覽器系統生態(哎媽呀,有種翻身農奴做主人的感覺O(∩_∩)O~~)。
傳統的html語義性的超文本標記語言已經很難承載更多的信息化數據,html5繼續深度發展,不再只是瀏覽器專用的標記語言,可能會成為跨平臺標準的信息表示層,信息表示多樣化前所未有,可能到時候不叫html了。
http2.0成為一個廣泛試用的標準,并進一步深化,安全校驗層做到類似區塊鏈去中心化的思路,做到極致安全(https估計可以下崗了)。
js成為跨平臺公認的標準實現語言(目前前端跨平臺基礎形態已經有了),隨著Es6的廣泛推廣和深度改進,可能就不會像現在這么靈活,更加像一個合格的標準“高級”腳本語言。
(以上猜想純屬個人理解,沒有權威認證,大家可以暢所欲言)
結語:剛來鵝廠不久,作為前端攻城獅進入到國內頂尖互聯網公司感到驕傲,性能優化是一個永恒的話題,每個階段都有聊不完的性能優化的話題,我將我這些年的一些不成文的理解整理了一下,希望對大家有點幫助。第一次發文章,有錯誤的地方請大家指點一二。
相關閱讀Web 前端性能優化 : 如何有效提升靜態文件的加載速度
使用 Skeleton Screen 提升用戶感知體驗
盒子端 CSS 動畫性能提升研究
此文已由作者授權騰訊云技術社區發布,轉載請注明文章出處
原文鏈接:https://cloud.tencent.com/com...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/112709.html
摘要:前端日報精選的作用鳥瞰前端再論性能優化翻譯給創始人和們的許可協議解惑如何工作引擎深入探究優化代碼的個技巧譯文第期還是,讓我來解決你的困惑中文基礎為什么比快二分查找法你真的寫對了嗎個人文章推薦機不可失直播技術盛宴,深圳騰訊開發者大 2017-09-21 前端日報 精選 setTimeout(fn, 0) 的作用鳥瞰前端 , 再論性能優化翻譯:給創始人和 CTO 們的 React 許可協議...
摘要:的內存分配方式修飾變量通常情況下,變量有個地方可以賦值直接賦值,構造函數中,或是初始化塊中。如就是對于變量,在聲明時,如果你沒有賦值,系統默認這是一個空白域,在構造函數進行初始化,如果是靜態的,則可以在初始化塊。 【java中為什么會有final變量】: final這個關鍵字的含義是這是無法改變的或者終態的; 那么為什么要阻止改變呢? java語言的發明者可能由于兩個目的而阻止改變: ...
摘要:由于原型即本身也是對象,所以原型繼承可認為是一種特殊的對象式繼承。原型繼承里的原型即是函數的特有屬性,原型繼承事先得有函數。揭秘魔術箱不論原型繼承還是對象式繼承,其核心技術是實現了對象實例的鏈。 JavaScript的原型繼承是老生常談。由于原型即prototype本身也是對象,所以原型繼承可認為是一種特殊的對象式繼承。對象式繼承是筆者基于自己的理解,所提出的一個名詞。本文就著重闡述這...
摘要:從運行結果可以看出,當子類繼承多個父類的時候,對于構造函數,只有第一個能夠被繼承,第二個就等掉了。重點看,類繼承了,同時,在構造函數中自己做了規定,也就是的構造函數是按照的意愿執行,不執行的內容,但是,還有一個方法,則繼承了這個方法。 在上一講代碼的基礎上,做進一步修改,成為了如下程序,請看官研習這個程序: #!/usr/bin/env python #coding:utf-8 c...
閱讀 1030·2021-09-22 15:26
閱讀 2607·2021-09-09 11:52
閱讀 1890·2021-09-02 09:52
閱讀 2241·2021-08-12 13:28
閱讀 1180·2019-08-30 15:53
閱讀 506·2019-08-29 13:47
閱讀 3379·2019-08-29 11:00
閱讀 3095·2019-08-29 10:58