高效快速地加载 AngularJS 视图
orzlong10
8年前
<p>当 AngularJS 应用程序变大时,很多问题就开始显现出来了,比如多层级视图的加载问题,如果在子视图显示之前没有预加载,则可能在需要展示时,发生视觉闪烁的情况。这种问题在网络缓慢,或者服务器使用较慢的 https 连接时更容易出现。</p> <p>本文将讨论更高效加载 AngularJS 视图的系统方法。</p> <h3>AngularJS 视图一般原理</h3> <p>AngularJS 视图也并不是什么特别神奇的技术,在其内部就是按普通的 directive 来处理的。也就是说,当一个位置需要显示 view 时,AngularJS 会尝试使用某种方法获得其 HTML 模板文件的具体内容、包装成 directive,执行 directive 的标准流程,最后添加到页面上。</p> <p><img src="https://simg.open-open.com/show/e1aefe38be93be8d1c084672a9675da8.png"></p> <p>回想一下,directive 本身是不是正好也支持 templateUrl 属性?这就与 view 技术衔接上了。</p> <p>这样说来,是不是视图模板也可以使用行内 DOM 甚至是字符串字面量值了呢?答案是肯定的!我们本来就可以使用一段行内 DOM 来作为 view 的模板。例如:</p> <p><img src="https://simg.open-open.com/show/10e438aa1a81bfad5fc0076e34d15c3c.png"></p> <p>当然,作为一个大型的 AngularJS 应用程序,将所有 view 都放在字符串值里,或者行内 DOM 里是不太现实的,我们希望可以使用多个小的 HTML 文件来作为子模板。这样,虽然整个应用很大,但每个子模板的文件并不大,一般都是几 KB 的小文件,当用户点击到指定位置,需要时使用对应界面的模板时再去加载,也就显著提高了效率。</p> <p>我们可以用下图来表示“行内 DOM”与“多个子模板文件”的性能对比:</p> <p><img src="https://simg.open-open.com/show/35b9586e6aeeadc461b477149842009f.png"></p> <h3>AngularJS 对视图加载的优化</h3> <p>上面提到了“多个子模板文件”的模板组织方式,这本是一件很平常、很自然的工作方式而已。也正是因此,才让人们感觉 AngularJS 工作方式与自己的期望的一致:因为在没有使用 AngularJS 之前,人们在开发一个 Web 应用时,页面就是这样一个个组织的。</p> <p>即使在以前,我们在提到性能的时候,自然会想到“缓存”。在以前,页面与页面之间的跳转使得每个页面都是相互独立的单位,因此页面内容的缓存只能有赖于浏览器了。而今,AngularJS 让所有页面子模板都在“单页应用”中加载,于是,我们在这个单页面应用内便获得了缓存页面内容的机会。AngularJS 中内建了缓存机制 templateCache:只要已经加载过某个页面子模板,就会在 templateCahce 中缓存起来,下次从服务器加载页面模板之前,先检查 templateCache,如果已有缓存则不需要从服务器上加载,直接使用。</p> <p><img src="https://simg.open-open.com/show/6ec44f9a3e4f2c3a4bd1ce80a3cf7a49.png"></p> <p>AngularJS 中内建了 templateCache 机制之后,加载视图的过程变得高效而轻松,Web 应用本身,以及开发者都不需要关心这一过程。不过,即使有页面内的 templateCache,页面模板在初次使用时还是需要从服务器加载,因此偶尔能见到一些视觉闪烁的情况,比如标签切换、页面跳转等。</p> <h3>对 AngularJS templateCache 的优化</h3> <p>作为一种优化手段,我们很自然能想到,既然页面能够在加载之后在 templateCache 起来就能提高性能,如果在应用启动之初 templateCache 中就有了所有页面的缓存,也就根本不需要服务器了,那么在页面需要显示时,也就基本不需要加载时间了。图可以变成这样:</p> <p><img src="https://simg.open-open.com/show/d6eb238f0c095198282cc8f6396e300b.png"></p> <p>要实现这一目标,只需要在发布应用之前,构建额外的 templates.js 文件,在其中将所有的页面模板读取出来并提前 put 到 templateCache 中,再将形成的 templates.js 嵌入到应用中即可在 Web 应用启动时就已经拥有所有页面模板内容的缓存版本了。</p> <p>不过,对于大型 AngularJS Web 应用来说,我们很快发现一个问题:这个 templates.js 文件本身的体积迅速大了起来,它又会成为一个新的性能问题。于是,我们可以使用另一个已有的经验:“异步加载”。有了异步加载的支持,在加载 templates.js 的请求还没有完成之前,可以“降级”使用 AngularJS 内建的机制,而一旦 templates.js 加载完成,就立即拥有了所有模板的缓存。</p> <p><img src="https://simg.open-open.com/show/ca9f8a8b23dbb81db9ef8070e301c1a6.png"></p> <p>理想中,templateCache 最好能达到最佳的性能表现,但实际应用中,如果不加优化,templates.js 文件本身的体积会令这种优化效果有所折扣,而加上异步加载 templates.js 和降级到逐个加载单个 htm 模板文件之后,又有了一些改善。</p> <h3>浏览器缓存</h3> <p>现在再来讨论一下浏览器缓存,可以结合上一节的 templates.js 一起来讨论了。浏览器缓存是浏览器里内置的一种缓存功能,当服务器正确配置了对 htm 和 js 文件的缓存支持时,浏览器将按指示缓存这些文件。不管是对一个个 htm 模板,还是对 templates.js,都可能被缓存。也就是说,只要在服务器上正确配置,那么上一节所述的“异步 templates.js”,以及“降级的多个 htm 模板文件”都可以被浏览器缓存。这样,我们将加载 htm 模板文件和 templates.js 的需求都减少到第一次使用应用之时。</p> <p>但在服务器上配置缓存也需要谨慎,如果配置不当,就会出现当服务器上文件已经更新,但客户端浏览器仍在使用老的缓存版本的问题。由于 AngularJS 应用使用绑定表达式显示界面,因此如果程序已经更新,而视图还是老版本,那么绑定表达式很可能失效。这种情况下,轻则局部界面错乱,重则整个 Web 应用完全无法使用。</p> <p><img src="https://simg.open-open.com/show/5c1b7145bc76453eb75c98549084200a.png"></p> <p>浏览器缓存原本是一个“杀手锏”,不管是只使用单个模板文件,还是使用 templateCache,浏览器缓存都可以极大地改善其性能效果。但一旦缓存配置不当致使客户端浏览器里使用了错误的版本,就直接导致应用错误,更不谈性能表现了。</p> <p>要处理缓存问题也有成熟的经验可供借鉴:也就是在文件名上使用版本号,每次需要更新文件内容时,同时更改版本号,那么整个文件名也就发生变化,也就不会发生缓存版本错误问题。结合上面的论述,我们在 templates.js 上添加上版本号,另一方面配置 AngularJS,在加载单个 htm 模板文件时,也会在请求上附上版本号,即可解决这一问题。当然,我们希望在开发时,标记要使用的视图模板时,不需要指定这个需要经常变化的版本号,从而最大程度地保障开发体验,并将维护成本降到最低。</p> <p><img src="https://simg.open-open.com/show/2ba847cc46e96d99adab5914f5bf0f0d.png"></p> <h3>总结</h3> <p>上面讨论了 AngularJS 视图各种可能的方式,分别实施的方法,以及其性能表现差异。主要值得关注的是经优化的 templateCache 机制,以及结合浏览器缓存的 templateCache 方法。总结来说,可以形成这样一个更直观的图形:</p> <p><img src="https://simg.open-open.com/show/b721c9700c6b95e0bb399adb680bead9.png"></p> <p>经过一番努力,最终我们能够达到这样的结果:</p> <ol> <li>在应用里添加仅在生产环境才生效的策略:支持在加载视图模板文件时在文件名中添加版本号(从页面中 templates.js 的文件路径中分析版本号)</li> <li>开发时不需要经过改变</li> <li>发布时预读取所有模板的内容,并生成带版本号的 templates.js,嵌入应用页面中</li> <li>在服务器上配置所有 htm 模板文件及 templates.js 的缓存策略为“允许缓存”</li> <li>用户首次使用应用时,集中所有网络带宽加载 AngularJS 基础脚本,以及应用程序业务逻辑系统,令应用程序尽早能够使用;此时应用使用 htm 模板文件作为视图模板</li> <li>异步加载 templates.js;加载完成之后应用开始使用页面内模板缓存</li> <li>用户再次使用应用时,从浏览器缓存中加载 templates.js</li> <li>再次发布应用时,修改 templates.js 文件名中的版本号,嵌入页面中</li> </ol> <p>所以,在首次用户使用应用时,其网络加载图形就像这样:</p> <p><img src="https://simg.open-open.com/show/4fb79954091884f76dd03e1d37aa8b25.png"></p> <p>最先加载的是应用程序 AngularJS 框架本身,以及业务逻辑,这时候应用已经可用;此时再异步去加载 templates.js 文件。事实上,上面的图形即是我们实际项目中的状况,具体实现在这里就不贴了,也欢迎读者一起探讨更多的可能性。</p> <p>从本文的讨论不难看出,只要通过各种方法,好好管理浏览器的加载行为,形成一个系统方法,便能令视图加载的性能表现变得更好。</p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959674299752170070" rel="nofollow">http://blog.jijiechen.com/post/loading-angularjs-view-fast-fast-fast</a></p> <p> </p>