高效快速地載入 AngularJS 檢視

ThoughtWorks發表於2016-06-29

當AngularJS應用程式變大時,很多問題就開始顯現出來了,比如多層級檢視的載入問題,如果在子檢視顯示之前沒有預載入,則可能在需要展示時,發生視覺閃爍的情況。這種問題在網路緩慢,或者伺服器使用較慢的https連線時更容易出現。

本文將討論更高效載入AngularJS檢視的系統方法。

AngularJS 檢視一般原理

AngularJS檢視也並不是什麼特別神奇的技術,在其內部就是按普通的directive來處理的。也就是說,當一個位置需要顯示view時,AngularJS會嘗試使用某種方法獲得其HTML模板檔案的具體內容、包裝成directive,執行directive的標準流程,最後新增到頁面上。

高效快速地載入 AngularJS 檢視

回想一下,directive本身是不是正好也支援templateUrl屬性?這就與view技術銜接上了。

這樣說來,是不是檢視模板也可以使用行內DOM甚至是字串字面量值了呢?答案是肯定的!我們本來就可以使用一段行內DOM來作為view的模板。例如:

高效快速地載入 AngularJS 檢視

當然,作為一個大型的AngularJS應用程式,將所有view都放在字串值裡,或者行內DOM裡是不太現實的,我們希望可以使用多個小的HTML檔案來作為子模板。這樣,雖然整個應用很大,但每個子模板的檔案並不大,一般都是幾KB的小檔案,當使用者點選到指定位置,需要時使用對應介面的模板時再去載入,也就顯著提高了效率。

我們可以用下圖來表示“行內DOM”與“多個子模板檔案”的效能對比:

高效快速地載入 AngularJS 檢視

AngularJS 對檢視載入的優化

上面提到了“多個子模板檔案”的模板組織方式,這本是一件很平常、很自然的工作方式而已。也正是因此,才讓人們感覺AngularJS工作方式與自己的期望的一致:因為在沒有使用AngularJS之前,人們在開發一個 Web應用時,頁面就是這樣一個個組織的。

即使在以前,我們在提到效能的時候,自然會想到“快取”。在以前,頁面與頁面之間的跳轉使得每個頁面都是相互獨立的單位,因此頁面內容的快取只能有賴於瀏覽器了。而今,AngularJS讓所有頁面子模板都在“單頁應用”中載入,於是,我們在這個單頁面應用內便獲得了快取頁面內容的機會。

AngularJS中內建了快取機制templateCache:只要已經載入過某個頁面子模板,就會在templateCahce中快取起來,下次從伺服器載入頁面模板之前,先檢查templateCache,如果已有快取則不需要從伺服器上載入,直接使用。

高效快速地載入 AngularJS 檢視

AngularJS中內建了templateCache 機制之後,載入檢視的過程變得高效而輕鬆,Web應用本身,以及開發者都不需要關心這一過程。不過,即使有頁面內的templateCache,頁面模板在初次使用時還是需要從伺服器載入,因此偶爾能見到一些視覺閃爍的情況,比如標籤切換、頁面跳轉等。

對AngularJS templateCache的優化

作為一種優化手段,我們很自然能想到,既然頁面能夠在載入之後在templateCache起來就能提高效能,如果在應用啟動之初templateCache中就有了所有頁面的快取,也就根本不需要伺服器了,那麼在頁面需要顯示時,也就基本不需要載入時間了。圖可以變成這樣:

高效快速地載入 AngularJS 檢視

要實現這一目標,只需要在釋出應用之前,構建額外的templates.js 檔案,在其中將所有的頁面模板讀取出來並提前put到templateCache中,再將形成的templates.js嵌入到應用中即可在Web應用啟動時就已經擁有所有頁面模板內容的快取版本了。

不過,對於大型AngularJS Web應用來說,我們很快發現一個問題:這個templates.js檔案本身的體積迅速大了起來,它又會成為一個新的效能問題。

於是,我們可以使用另一個已有的經驗:“非同步載入”。有了非同步載入的支援,在載入templates.js 的請求還沒有完成之前,可以“降級”使用AngularJS內建的機制,而一旦templates.js載入完成,就立即擁有了所有模板的快取。

高效快速地載入 AngularJS 檢視

理想中,templateCache最好能達到最佳的效能表現,但實際應用中,如果不加優化,templates.js檔案本身的體積會令這種優化效果有所折扣,而加上非同步載入 templates.js和降級到逐個載入單個htm模板檔案之後,又有了一些改善。

瀏覽器快取

現在再來討論一下瀏覽器快取,可以結合上一節的templates.js一起來討論了。瀏覽器快取是瀏覽器裡內建的一種快取功能,當伺服器正確配置了對htm和js檔案的快取支援時,瀏覽器將按指示快取這些檔案。不管是對一個個htm模板,還是對templates.js,都可能被快取。

也就是說,只要在伺服器上正確配置,那麼上一節所述的“非同步 templates.js”,以及“降級的多個htm模板檔案”都可以被瀏覽器快取。這樣,我們將載入htm模板檔案和templates.js的需求都減少到第一次使用應用之時。

但在伺服器上配置快取也需要謹慎,如果配置不當,就會出現當伺服器上檔案已經更新,但客戶端瀏覽器仍在使用老的快取版本的問題。由於AngularJS應用使用繫結表示式顯示介面,因此如果程式已經更新,而檢視還是老版本,那麼繫結表示式很可能失效。這種情況下,輕則區域性介面錯亂,重則整個Web應用完全無法使用。

高效快速地載入 AngularJS 檢視

瀏覽器快取原本是一個“殺手鐗”,不管是隻使用單個模板檔案,還是使用templateCache,瀏覽器快取都可以極大地改善其效能效果。但一旦快取配置不當致使客戶端瀏覽器裡使用了錯誤的版本,就直接導致應用錯誤,更不談效能表現了。

要處理快取問題也有成熟的經驗可供借鑑:也就是在檔名上使用版本號,每次需要更新檔案內容時,同時更改版本號,那麼整個檔名也就發生變化,也就不會發生快取版本錯誤問題。結合上面的論述,我們在templates.js 上新增上版本號,另一方面配置AngularJS,在載入單個htm模板檔案時,也會在請求上附上版本號,即可解決這一問題。當然,我們希望在開發時,標記要使用的檢視模板時,不需要指定這個需要經常變化的版本號,從而最大程度地保障開發體驗,並將維護成本降到最低。

高效快速地載入 AngularJS 檢視

總結

上面討論了AngularJS檢視各種可能的方式,分別實施的方法,以及其效能表現差異。主要值得關注的是經優化的templateCache機制,以及結合瀏覽器快取的templateCache方法。總結來說,可以形成這樣一個更直觀的圖形:

經過一番努力,最終我們能夠達到這樣的結果:

高效快速地載入 AngularJS 檢視

  1. 在應用裡新增僅在生產環境才生效的策略:支援在載入檢視模板檔案時在檔名中新增版本號(從頁面中templates.js的檔案路徑中分析版本號);
  2. 開發時不需要經過改變;
  3. 釋出時預讀取所有模板的內容,並生成帶版本號的templates.js,嵌入應用頁面中;
  4. 在伺服器上配置所有htm模板檔案及templates.js的快取策略為“允許快取”;
  5. 使用者首次使用應用時,集中所有網路頻寬載入AngularJS基礎腳;本,以及應用程式業務邏輯系統,令應用程式儘早能夠使用;此時應用使用htm模板檔案作為檢視模板;
  6. 非同步載入templates.js;載入完成之後應用開始使用頁面內模板快取;
  7. 使用者再次使用應用時,從瀏覽器快取中載入templates.js;
  8. 再次釋出應用時,修改templates.js 檔名中的版本號,嵌入頁面中。

所以,在首次使用者使用應用時,其網路載入圖形就像這樣:

高效快速地載入 AngularJS 檢視

最先載入的是應用程式AngularJS框架本身,以及業務邏輯,這時候應用已經可用;此時再非同步去載入templates.js檔案。事實上,上面的圖形即是我們實際專案中的狀況,具體實現在這裡就不貼了,也歡迎讀者一起探討更多的可能性。

從本文的討論不難看出,只要通過各種方法,好好管理瀏覽器的載入行為,形成一個系統方法,便能令檢視載入的效能表現變得更好。

相關文章