当前位置: 首页 > 科技观察

优化AngularJS:1200毫秒到35毫秒的蜕变

时间:2023-03-13 19:13:26 科技观察

在Scalyr,我们最近着手重写我们的Web客户端。ScalyrLogs是一种多用途监控和日志分析工具,可在我们的专用日志数据库中执行大多数查询。几十毫秒,但是每次页面响应都需要加载页面,可能需要几秒才能呈现给用户。单页设计架构保证不会拖累后??台的强大性能,于是我们开始寻找合适的框架,AngularJS脱颖而出。遵循“failfast”的原则,我们开始了挑战之旅:日志视图Write的重新设计。测试应用程序框架确实是一项严峻的挑战。当用户点击日志中的任意一个词时,我们需要搜索相关信息,而页面上可以点击的元素数不胜数;我们希望日志的分页功能能够得到即时的反馈。其实我们已经提前拿到了下一页的日志数据,所以用户界面的更新就成了瓶颈。如果使用AngularJS直接实现日志视图的换页功能,需要1.2秒,但如果细心优化,可以降低到35毫秒。这些优化被证明也适用于应用程序的其他部分,并且也很好地适应了AngularJS。但是我们必须打破一些规则来实现我们的想法,稍后讨论。一个Github更新的日志演示AnAngularJS日志查看器本质上,日志视图是一个日志消息列表,每个单词都是可点击的。所以给DOM元素添加Angular指令,简单实现如下:}单页应用有上千个token是很正常的,在早期测试中我们发现下一页进入log需要几秒才能执行JavaScript。更糟糕的是,不相关的操作(比如点击导航下拉框)没有延迟。AngularJS的高手说过,数据元素绑定的个数最好控制在200以下,对于我们来说,一个词就是一个元素的地方,这个数量已经远远超过了这个数量。使用Chrome的JavaScriptprofiler工具进行分析,我们可以快速定位到两个拖延点。首先,每次更新都需要花费大量时间来创建和销毁DOM元素。如果新视图的行数不同,或者任何一行的单词数不同,Angular的ng-repeat指令将创建或销毁DOM元素。价格太高了。其次,每个单词都有自己的changewatcher,AngularJS会监视这些单词,一旦鼠标点击就会触发,这是影响无关操作延迟的罪魁祸首(下拉菜单导航)。优化#1:缓存DOMelements我们创建了ng-repeat指令的变体。在我们的版本中,如果绑定数据的数量减少,多余的DOM元素将被隐藏而不是销毁。如果过段时间元素数量减少了又增加了,我们就会重用这些缓存的元素。优化#2:Aggregatewatchers花在调用changewatchers上的所有时间大部分都被浪费了。在我们的应用程序中,特定词的数据绑定永远不会改变,除非整个日志消息发生变化。为此,我们创建了一个指令“hides”,它隐藏了子元素的changewatchers,并且仅在特定的父元素表达式发生变化时调用它们。通过这种方式,我们避免了每次鼠标点击或其他小修改时的全面变化观察者(为了实现这个想法,我们稍微修改了AngularJS抽象层,我们将在后面详细说明)。优化#3:推迟元素创建如上所述,我们为日志中的每个单词构建了一个DOM,我们可以为每一行使用单个DOM元素来获得相同的视觉呈现;其他元素是响应鼠标点击操作的,因此,我们决定推迟这部分的创建,只在鼠标移动到某一行时才创建。为此,我们为每一行创建了两个版本,一个是简单的文本元素,用于显示完整的日志消息,另一行是占位符,用于显示填写每个单词的最终效果。这个占位符一开始是隐藏的,只有当鼠标移到那一行时才会显示,此时简单的文本行会被隐藏。正如我们将在下面看到的,显示占位符是填充单词元素的方式。优化#4:避免监控隐藏元素我们创建了另一个指令来防止监控隐藏元素,该指令支持优化#1,我们有比原始数据更多的隐藏DOM节点,因此必须消除对额外DOM节点的监控。这也支持优化#3,从而更容易推迟创建单词节点。因为在这行数据的标记化版本出现之前,我们不会创建它。以下代码是所有优化后的样子,我们的自定义指令以粗体显示。{{logLine|formatLine}}{{token|formatToken}}

Sly-repeat是ng-repeat的一个变体,用于隐藏额外的DOM元素而不是销毁它们,sly-evaluate-only-when防止内部changewatchers除非“logLines”变量修改,sly-prevent-evaluation-when-hidden主要负责当鼠标移过指定行时显示隐藏的div。这体现了AngularJS对封装和分离的控制,我们在不影响模板结构的情况下做了复杂的优化(这里展示的代码不是实际的生产代码,但它展示了所有的要点)。结果我们来看看效果,我们加了一些代码来衡量,从鼠标点击开始,一直到Angular的$digest循环结束(意思是更新DOM结束)。我们通过Tomcat日志测量单击“下一页”按钮的性能。环境使用MacBookPro上的Chrome。结果如下表所示(每个数据为10次测试的平均值):数据已缓存从服务器获取数据简单实现1190ms1300ms优化后35ms201ms这些数据不包括浏览器花在DOM布局和重绘上的时间(JavaScript执行完成后),每次约30毫秒。尽管如此,效果还是很明显的;下一页的响应时间从1200毫秒骤降到35毫秒(如果包括渲染,则为65毫秒)。“从服务器获取数据”中的数据包括我们使用AJAX从后台获取日志数据的时间。这与单击下一页按钮不同,因为我们预取了下一页日志数据,但它可能适用于其他UI响应。即使这样,优化后的程序也可以实时更新。综上,这些代码运行了两个月,效果还是比较满意的。如果想看实际效果,请到scalyr.com点击页面底部的“TryTheDemo”链接,然后点击“LogView”,尝试下一页按钮。很快,对吧?您无法相信这是从正在运行的服务器上看到的实时数据。实现上述优化确实花费了很多时间。看起来我们创建了一个自定义指令来生成所有日志视图,绕过ng-repeat。但是,这些都违背了AngularJS的精神,你要承担代码维护成本、测试成本等因素。由于日志视图是我们对AngularJS的测试项目,我们需要验证这个方案的可行性。此外,我们创建的新指令已在应用程序的其他部分使用。我们尽最大努力实现Angular的精神,但我们必须对AngularJS抽象层进行更改才能实现这些优化。我们重写Scope的$watch来拦截watcher的注册,然后就要小心操作Scope的实例变量来控制$digest进程中watcher的运行。下一次,本文将讨论一系列技术要点。我们是效率最大化的忠实拥护者。上面介绍的优化只是我们使用的一些小技巧。在后续文章中,我们将继续讨论如何减少网络请求、网络延迟、服务器执行时间等。当然我们在开发自己的应用程序时会继续讨论AngularJS是如何构建的。如果您对这些感兴趣,请留下您的宝贵意见。强大的广告插入在Scalyr,我们一直致力于通过优秀的技术来改善DevOps体验。既然您已经阅读了本文,请前往scalyr.com看看我们已经走了多远!原文链接:http://blog.scalyr.com/2013/10/31/angularjs-1200ms-to-35ms/翻译链接:http://blog.jobbole.com/51180/