高效快速地加載 AngularJS 視圖
當 AngularJS 應用程序變大時,很多問題就開始顯現出來了,比如多層級視圖的加載問題,如果在子視圖顯示之前沒有預加載,則可能在需要展示時,發生視覺閃爍的情況。這種問題在網絡緩慢,或者服務器使用較慢的 https 連接時更容易出現。
本文將討論更高效加載 AngularJS 視圖的系統方法。
AngularJS 視圖一般原理
AngularJS 視圖也并不是什么特別神奇的技術,在其內部就是按普通的 directive 來處理的。也就是說,當一個位置需要顯示 view 時,AngularJS 會嘗試使用某種方法獲得其 HTML 模板文件的具體內容、包裝成 directive,執行 directive 的標準流程,最后添加到頁面上。
回想一下,directive 本身是不是正好也支持 templateUrl 屬性?這就與 view 技術銜接上了。
這樣說來,是不是視圖模板也可以使用行內 DOM 甚至是字符串字面量值了呢?答案是肯定的!我們本來就可以使用一段行內 DOM 來作為 view 的模板。例如:
當然,作為一個大型的 AngularJS 應用程序,將所有 view 都放在字符串值里,或者行內 DOM 里是不太現實的,我們希望可以使用多個小的 HTML 文件來作為子模板。這樣,雖然整個應用很大,但每個子模板的文件并不大,一般都是幾 KB 的小文件,當用戶點擊到指定位置,需要時使用對應界面的模板時再去加載,也就顯著提高了效率。
我們可以用下圖來表示“行內 DOM”與“多個子模板文件”的性能對比:
AngularJS 對視圖加載的優化
上面提到了“多個子模板文件”的模板組織方式,這本是一件很平常、很自然的工作方式而已。也正是因此,才讓人們感覺 AngularJS 工作方式與自己的期望的一致:因為在沒有使用 AngularJS 之前,人們在開發一個 Web 應用時,頁面就是這樣一個個組織的。
即使在以前,我們在提到性能的時候,自然會想到“緩存”。在以前,頁面與頁面之間的跳轉使得每個頁面都是相互獨立的單位,因此頁面內容的緩存只能有賴于瀏覽器了。而今,AngularJS 讓所有頁面子模板都在“單頁應用”中加載,于是,我們在這個單頁面應用內便獲得了緩存頁面內容的機會。AngularJS 中內建了緩存機制 templateCache:只要已經加載過某個頁面子模板,就會在 templateCahce 中緩存起來,下次從服務器加載頁面模板之前,先檢查 templateCache,如果已有緩存則不需要從服務器上加載,直接使用。
AngularJS 中內建了 templateCache 機制之后,加載視圖的過程變得高效而輕松,Web 應用本身,以及開發者都不需要關心這一過程。不過,即使有頁面內的 templateCache,頁面模板在初次使用時還是需要從服務器加載,因此偶爾能見到一些視覺閃爍的情況,比如標簽切換、頁面跳轉等。
對 AngularJS templateCache 的優化
作為一種優化手段,我們很自然能想到,既然頁面能夠在加載之后在 templateCache 起來就能提高性能,如果在應用啟動之初 templateCache 中就有了所有頁面的緩存,也就根本不需要服務器了,那么在頁面需要顯示時,也就基本不需要加載時間了。圖可以變成這樣:
要實現這一目標,只需要在發布應用之前,構建額外的 templates.js 文件,在其中將所有的頁面模板讀取出來并提前 put 到 templateCache 中,再將形成的 templates.js 嵌入到應用中即可在 Web 應用啟動時就已經擁有所有頁面模板內容的緩存版本了。
不過,對于大型 AngularJS Web 應用來說,我們很快發現一個問題:這個 templates.js 文件本身的體積迅速大了起來,它又會成為一個新的性能問題。于是,我們可以使用另一個已有的經驗:“異步加載”。有了異步加載的支持,在加載 templates.js 的請求還沒有完成之前,可以“降級”使用 AngularJS 內建的機制,而一旦 templates.js 加載完成,就立即擁有了所有模板的緩存。
理想中,templateCache 最好能達到最佳的性能表現,但實際應用中,如果不加優化,templates.js 文件本身的體積會令這種優化效果有所折扣,而加上異步加載 templates.js 和降級到逐個加載單個 htm 模板文件之后,又有了一些改善。
瀏覽器緩存
現在再來討論一下瀏覽器緩存,可以結合上一節的 templates.js 一起來討論了。瀏覽器緩存是瀏覽器里內置的一種緩存功能,當服務器正確配置了對 htm 和 js 文件的緩存支持時,瀏覽器將按指示緩存這些文件。不管是對一個個 htm 模板,還是對 templates.js,都可能被緩存。也就是說,只要在服務器上正確配置,那么上一節所述的“異步 templates.js”,以及“降級的多個 htm 模板文件”都可以被瀏覽器緩存。這樣,我們將加載 htm 模板文件和 templates.js 的需求都減少到第一次使用應用之時。
但在服務器上配置緩存也需要謹慎,如果配置不當,就會出現當服務器上文件已經更新,但客戶端瀏覽器仍在使用老的緩存版本的問題。由于 AngularJS 應用使用綁定表達式顯示界面,因此如果程序已經更新,而視圖還是老版本,那么綁定表達式很可能失效。這種情況下,輕則局部界面錯亂,重則整個 Web 應用完全無法使用。
瀏覽器緩存原本是一個“殺手锏”,不管是只使用單個模板文件,還是使用 templateCache,瀏覽器緩存都可以極大地改善其性能效果。但一旦緩存配置不當致使客戶端瀏覽器里使用了錯誤的版本,就直接導致應用錯誤,更不談性能表現了。
要處理緩存問題也有成熟的經驗可供借鑒:也就是在文件名上使用版本號,每次需要更新文件內容時,同時更改版本號,那么整個文件名也就發生變化,也就不會發生緩存版本錯誤問題。結合上面的論述,我們在 templates.js 上添加上版本號,另一方面配置 AngularJS,在加載單個 htm 模板文件時,也會在請求上附上版本號,即可解決這一問題。當然,我們希望在開發時,標記要使用的視圖模板時,不需要指定這個需要經常變化的版本號,從而最大程度地保障開發體驗,并將維護成本降到最低。
總結
上面討論了 AngularJS 視圖各種可能的方式,分別實施的方法,以及其性能表現差異。主要值得關注的是經優化的 templateCache 機制,以及結合瀏覽器緩存的 templateCache 方法。總結來說,可以形成這樣一個更直觀的圖形:
經過一番努力,最終我們能夠達到這樣的結果:
- 在應用里添加僅在生產環境才生效的策略:支持在加載視圖模板文件時在文件名中添加版本號(從頁面中 templates.js 的文件路徑中分析版本號)
- 開發時不需要經過改變
- 發布時預讀取所有模板的內容,并生成帶版本號的 templates.js,嵌入應用頁面中
- 在服務器上配置所有 htm 模板文件及 templates.js 的緩存策略為“允許緩存”
- 用戶首次使用應用時,集中所有網絡帶寬加載 AngularJS 基礎腳本,以及應用程序業務邏輯系統,令應用程序盡早能夠使用;此時應用使用 htm 模板文件作為視圖模板
- 異步加載 templates.js;加載完成之后應用開始使用頁面內模板緩存
- 用戶再次使用應用時,從瀏覽器緩存中加載 templates.js
- 再次發布應用時,修改 templates.js 文件名中的版本號,嵌入頁面中
所以,在首次用戶使用應用時,其網絡加載圖形就像這樣:
最先加載的是應用程序 AngularJS 框架本身,以及業務邏輯,這時候應用已經可用;此時再異步去加載 templates.js 文件。事實上,上面的圖形即是我們實際項目中的狀況,具體實現在這里就不貼了,也歡迎讀者一起探討更多的可能性。
從本文的討論不難看出,只要通過各種方法,好好管理瀏覽器的加載行為,形成一個系統方法,便能令視圖加載的性能表現變得更好。
來自: http://blog.jijiechen.com/post/loading-angularjs-view-fast-fast-fast