`
liujiekasini0312
  • 浏览: 147491 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

大型应用下的 AngularJS 性能

 
阅读更多

1 介绍

无论你是为一个拥有大量用户的旧应用编写一个Angalar前端,或已有的Angular应用正在迅速扩张,性能都是一个重要方面。理解什么会导致AngularJS应用程序响应变慢,并且知道在开发过程中对此做出一些权衡是非常重要的。本文将讲述一些AngularJS可能导致的常见性能问题,以及给出在未来如何修复和避免他们的建议。

1.1 需求,假设

本文假设对JavaScript编程语言和AngularJS比较熟悉。当使用特定于版本的特性,他们会被标注。如果你已经花了一些时间在玩Angular,但还没有认真地处理性能问题,那么你最能吸收这篇文章的要义。

2 行业工具

2.1 基准分析

最出色的代码基准测试工具就是jsPerf。为了增强可读性,我将在后面相关的部分链接到特定的test runs(测试例子)。

2.2 性能分析

Chrome开发工具有一个很棒的Javascript分析器。我强烈推荐阅读本系列文章。

2.3 Angular Batarang

由Angular核心团队维护的一个专用的Angular调试器, 在GitHub上可以获取。

3 软件性能

有两个导致软件性能差的根本原因。
第一个是算法的时间复杂度。解决这个问题很大程度上超出了本文的范围,一般可以这样说,时间复杂度是衡量一个程序需要做多少次的比较来实现一个结果。比较数量越多,程序越慢。一个简单的例子是线性查找与二分查找。线性查找对于同一组数据需要进行更多的比较,因此会慢。时间复杂度的详细讨论,请参考维基百科文章

第二个原因是空间复杂度。这是一台电脑运行你的解决方案需要多少“空间”或内存的测量。需要的内存越多,解决方案就越慢。本文将讨论的大多数问题,围绕空间复杂度。详细讨论,请参阅这里.。

4 Javascript性能

有些要说的是关于Javascript性能,这里并不局限于Angular。

4.1 循环

避免在一个循环中调用外部函数。一旦任何调用可以在循环外部的完成,它将大大加速你的系统。例如:

上面将大大慢于下面:

http://jsperf.com/for-loop-perf-demo-basic

4.2 Dom访问

需要着重注意的是访问DOM是昂贵的。

虽然这在AngularJS应该不是一个问题,意识到这一点仍然是有用的。这里说的第二件事是,DOM树应该保持尽可能小。

最后,如果可能的话,避免修改DOM,不设置内联样式。这是由于JavaScript重排。重排的深度的讨论超出了本文的范围,但是这里可以找到一个不错的参考。

4.3变量作用域和垃圾收集

所有变量作用域尽可能紧密,会让JavaScript垃圾收集器尽早地释放你的内存。这是通常JavaScript,特别是Angular缓慢,延迟,不响应的一个极其常见原因。请注意以下问题:

函数终止时,将没有必要进一步引用b,垃圾收集器将释放内存。然而,如果在其他地方有这样一行代码:

我们现在将对象绑定到一个变量同时保持引用,阻止垃圾收集器回收它。虽然这可能是必要的,重要的是你要知道对象引用有什么影响。

4.4数组和对象

有许多事情要谈。首先且最简单的是,数组总是比对象更快,数字访问好于非数字访问。

上面快于下面

还快于

http://jsperf.com/array-vs-object-perf-demo

此外,请注意,基于V8的现代浏览器,有较少属性的对象会使用一种特殊的表现形式,来提高他们的访问速度,所以试着保持属性的数量最小化。也请注意,尽管JavaScript数组中可以使用混合类型,并不意味着这是一个好主意:

任何对第二项的操作都将明显慢于对第一项,这不仅仅是因为逻辑需要更复杂。

http://jsperf.com/array-types-compare-perf

避免使用delete。例如,给定:

任何对arrDelete的迭代将慢于对arr的相同迭代。

http://jsperf.com/delet-is-slow

这将会在数组中创建一个坑,大大降低操作的性能。

5 重要概念

既然我们已经讨论了JavaScript性能,它对理解一些Angular背后的关键概念很重要。

5.1Scopes(作用域)和Digest周期

AngularScopes本质上是JavaScript对象。他们遵循一个预定义的原型继承规则,深入讨论超出了本文的范围。与本文有关系的,如前所述,使用小Scopes比大Scopes更快。

另一个可以得出的结论是,任何时间,一个新的Scopes被创建,就会为垃圾收集器增加更多需要收集的值。

编写普通的且性能特别的AngularJS应用程序时,digest周期特别重要。实际上,每一个scope中都存储了一个$$watchers函数数组。

每次对scope中的值调用$watch函数,或者一个值被插入或绑定到DOM上,如使用ng-repeat,ng-switch,ng-if,或者其他的DOM属性或元素,都会在最里层的scope的$$watchers数组中添加一个函数。

当scope里的任何值发生变化时,$$watchers中的所有watcher将被触发,如果其中任何一个修改了一个被检测的值, 他们将再次被触发。这将继续下去,直到$$watchers数组不在有任何变化,或AngularJS抛出一个异常。

另外,如果非Angular代码通过$scope.$apply()运行。这将立即启动digest周期。

最后注意的是,$scope.evalAsync()将在一个异步循环中执行,它不会触发一个新的Digest周期, 它将运行在当前或下一个digest周期的末尾。

6 常见问题:用心设计Angular

6.1大型对象和服务器调用。

所以这一切教会了我们什么?首先,我们应该思考我们的数据模型,努力限制对象的复杂性。这对从服务器返回的对象特别重要。

简单地将整个数据库行强制.toJson()是非常诱人。这里必须要强调:请不要这样做。

使用一个自定义的序列化器,返回Angular应用程序必要的属性的子集。

6.2监视函数

另一个常见的问题是在watcher或绑定中使用函数。不要将任何指令(ng-show ng-repeat,等等)直接绑定函数。不要直接监测函数的结果。该函数将在每个digest周期运行,这极有可能降缓你的程序。

6.3监视对象

类似的,Angular能够通过将scope.$watch第三个可选的参数设置为true来监视整个对象。说句不好听,这是一个非常糟糕的想法。一个更好的解决方案是依靠服务和对象引用,在scope之间传播对象的变化。

7列表问题

7.1大型列表

如果可能的话,避免大型列表。ng-repeat会做一些相当沉重的DOM操作(更不用说污染$$watchers),所以无论是通过分页或无限滚动,试着保持任何列表的渲染使用小型数据。

7.2过滤器

如果可能的话,避免使用过滤器。他们每个digest循环运行两次,一次是当发生任何变化,另一次是收集进一步的改变,实际上,不从内存删除任何子集,而是简单地用css来过滤。

$index没有什么价值,因为它不再对应于实际的数组索引,而是排序后数组的索引。它还会阻止你释放所有列表的scope。

7.3 更新ng-repeat

同样重要的是避免使用ng-repeat时进行全局列表刷新。在内部,ng-repeat将产生一个$$ hashKey属性,用它来作为集合中的识别项。这意味着做一些像scope.listBoundToNgRepeat = serverFetch()的操作将导致对整个列表进行一个完整的重新计算,导致对每个个体元素的transcludes运行以及watchers触发。这是一个非常昂贵的做法。

有两种方法可以解决这个问题。一是维护两个集合和在过滤后的集合上使用ng-repeat(更通用的,需要定制同步逻辑,因此算法更复杂和难以的维护),另一种是使用track by来指定自己的key(需要Angular 1.2+,通用性略低于前者,不需要自定义同步逻辑)。

简而言之

<footer style="padding: 40px 0px 0px; border-width: 1px 0px 0px; border-top-style: solid; border-top-color: rgb(238, 238, 238); margin: 0px; background: rgb(248, 248, 248);">将比下面慢:</footer>

将比简单添加慢:

换成:

所有的三种方法的一个完整的功能的演示可以在这里找到.

点击这三种方法,可以很明显地重新演示这个问题。一方面要注意的,track方法只有在迭代对象的一个字段能在集合中保证唯一时才能被使用。对于服务器数据,id属性可以作为天生的tracker。如果没有这个条件,不幸的是,只有自定义同步逻辑是唯一的出路。

8. 渲染问题

Angular 应用缓慢的常见来源是在ng-if或ng-switch上不正确使用ng-hide和ng-show 。区别是重要的,重要性不能在性能的上下文中被夸大。

ng-hide和ng-show简单地切换CSS display属性。在实践中这意味着任何显示或隐藏仍将在页面上,尽管看不见。任何scope将存在,所有$$watchers将触发。

ng-if和ng-switch实际上完全删除或添加DOM。使用用ng-if删除的东西不在scope中。性能优势现在应该很明显,但也是有考究的。具体地说,切换show/hide相对便宜,但切换if/switch相对昂贵。不幸的是这就导致了需要根据不同情况判断使用不同的调用。做出这个决定需要回答的问题是:

  1. How frequently will this change? (the more frequent, the worse fitng-ifis).
  2. 这经常会如何变化?(越频繁,使用ng-if越糟糕)。
  3. How heavy is the scope? (the heavyer, the better fitng-ifis).
  4. scope有多大?(越大,越是使用ng-if)。

9. Digest周期问题

9.1 绑定

试着减少你的绑定。Angular1.3中,有一个新的只进行一次的绑定,语法形状为{ {::scopeValue } }。这将从scope中拿取一次,而不会添加一个watcher到watchers数组。

9.2$digest()and$apply()

scope.$apply是一个强大的工具,它允许你将值从Angular外部引入到你的应用程序。本质上它会Angular的所有事件(ng-click等)上被触发。问题出现在,scope.$apply始于$rootScope,同时贯穿整个scope链,将导致每个scope触发每个watcher。

另一方面scope.$digest只会在指定的scope内触发,只往下传递。性能优势应该相当不证自明的。折中的方案,当然是,任何父scope将不会收到这个更新,直到下一个循环周期。

9.3$watch()

scope.$watch()现在已经讨论了好几次。一般来说,scope.$watch()表明糟糕的体系结构。大部分情况下,较低开销的服务和引用的一些组合绑定就能达到相同的结果。如果您必须创建一个watcher,永远记住尽可能地早点解除绑定。你可以通过调用由$watch返回的解绑函数来解除一个watcher,。

如果你不能过早地解除绑定,记得在$on(‘$destroy’)解除绑定

9.4$on,$broadcast, and$emit

像$watch,这些都是慢的,因为事件(可能)遍历你的整个scope的层次结构。除此之外,它们会像GOTO一样,让您的应用程序很难调试。幸运的是,像$watch,如果有必要他们可以通过返回函数解除绑定(记得在$on('$destroy')解除绑定,同时可以通过正确地使用服务和scope继承来完全避免)。

9.5$destroy

如前所述,你应该总是显式调用$on(‘$destroy’),解绑你所有的watchers和事件监听器,并取消任何$timeout实例,或其他正在进行的异步交互。这不仅是确保安全良好的实践,同时标记你的scope让垃圾收集更迅速。不这样做会让他们在后台运行,浪费CPU和RAM。

尤其重要的是要记住的在$destroy调用中解绑任何在指令元素上定义的DOM事件监听器。不这样做,将会导致在旧浏览器发生内存泄漏和在现代浏览器发生垃圾收集缓慢。一个非常重要的结论是,你需要记住在你移除DOM前调用scope.$destroy。

9.6$evalAsync

scope.$evalAsync是一个强大的工具,它让你把要执行的操作在当前digest周期的末尾进行排队,不会导致在scope修改后的另一个digest周期。这需要基于具体案例思考,但预期的效果,evalAsync可以大大提高页面的性能。

10 指令问题

10.1 隔离的Scope和Transclusion

隔离Scope和Transclusion是Angular最令人兴奋的一些事情。他们允许构建可重用、封装的组件,它们在语法上和概念上优雅,让Angular出彩的一个核心部分。

然而,他们有一个权衡。默认情况下,指令不创建一个scope,而是拥有和他们的父元素相同的范围。通过创建一个新的隔离scope或Transclusion,来创建一个新的对象去跟踪和添加新的watch,因此会减慢我们的应用程序。总是在你使用它前停下来并思考有没有必要。

10.2 编译周期

指令编译功能在附加scope之前运行,这是运行任何DOM操作(例如绑定事件)的最佳的地方。从性能的角度来看,需要重要认识的,是元素和属性传递到编译函数使用了原始html模板,它在任何Angular变化前。在实践中这意味着,DOM操作完成,将运行一次,直接使用。另一个重要的点事prelink和postlink的区别。简而言之,prelinks运行由外而内,而postlinks运行由内而外。因此,prelinks提供轻微的性能提升,因为他们阻止内部指令运行第二次digest周期,当父节点在prelink修改scope。然而,子DOM可能不可用。

11 DOM事件问题

Angular提供了许多预先编译好的DOM事件指令. ng-click,ng-mouseenter,ng-mouseleave等等。每次这些事件触发时都会调用scope.$apply()。一个更有效的方法是直接使用addEventListener绑定,然后必要时使用scope.$digest。

12 总结

12.1 AngularJS:不好的部分

  • ng-click和其他DOM事件
  • scope.$watch
  • scope.$on
  • 指令postLink
  • ng-repeat
  • ng-show and ng-hide

12.2AngularJS:好的(性能)部分

  • track by
  • 使用::只绑定一次
  • compile和preLink
  • $evalAsync
  • 服务,作用域继承,通过引用传递对象
  • $destroy
  • 解绑watches和事件监听器
  • ng-if和ng-switch

了解更多,https://www.airpair.com/angularjs/posts/angularjs-performance-large-applications#WLXbzSUvg6aWzlUP.99

分享到:
评论

相关推荐

    AngularJs搭建大型多页面应用

    ### AngularJs搭建大型多页面应用 #### AngularJs简介与历史发展 AngularJs是Google于2010年推出的一款开源JavaScript框架,它旨在简化Web应用的开发过程,并提高其维护性。随着Web技术的发展,前后端开发逐渐分离...

    Angularjs 合集 Angularjs 合集

    这可能包括深入讲解Scope、Controller、Directives和Filters的内部工作机制,以及如何利用AngularJS的模块系统构建大型应用。可能还会讨论AngularJS与其它技术如RESTful API、Bootstrap或jQuery的集成,以及如何使用...

    AngularJS书两本

    模块化有助于组织大型应用,而路由则允许在单页应用(SPA)中实现页面导航。在指令方面,除了基础用法,还可能涉及指令的编译和链接过程、属性绑定、以及如何编写复杂的自定义指令。关于$scope,它是连接控制器和...

    JavaScript开发-AngularJs高性能绑定

    **JavaScript开发-AngularJS高性能绑定** 在JavaScript开发领域,AngularJS是一款广泛使用的前端MVC框架,它极大地简化了Web...在大型或者对性能要求较高的AngularJS项目中,理解和掌握这样的性能优化技巧至关重要。

    精通AngularJS (美)[书签]

    标题《精通AngularJS》和描述中所提到的“精通AngularJS”指的是一本专注于教授读者如何深入理解和应用AngularJS这一现代JavaScript框架的书籍。AngularJS是谷歌开发的开源前端Web应用程序框架,它是为了解决单页...

    AngularJs 1.5.8

    总的来说,AngularJS 1.5.8是一个成熟的版本,它的功能强大且全面,适合大型企业级应用的开发。虽然后来Angular(2+)版本发布,但AngularJS 1.x系列依然有大量项目在运行,对于学习和维护这些项目,理解AngularJS ...

    angularjs1.2最新

    9. **性能优化**:1.2版本针对大型应用进行了性能优化,减少了内存占用,提升了渲染速度,尤其是对于大数据集的处理。 10. **社区支持**:AngularJS 1.2拥有庞大的开发者社区,丰富的教程、插件和库资源,为开发者...

    Build Your Own Angularjs完整版

    在构建大型应用时,性能优化是必不可少的。书中会介绍AngularJS的性能优化技巧,如延迟加载、$digest 循环的理解以及如何避免不必要的DOM操作,以提升应用的响应速度。 最后,你将了解到AngularJS的最新发展,包括...

    9种改善AngularJS性能的方法

    在本文中,我们将探讨9种有效提升AngularJS性能的方法,这对于任何使用AngularJS构建Web应用的开发者来说都至关重要。AngularJS作为一个广泛采用的前端框架,其性能优化是确保应用程序高效运行的关键。 首先,我们...

    angularjs_requirejs demo

    为了深入理解这个示例,你需要打开这个文件夹,查看各个文件的内容,尤其是`main.js`(或类似配置文件)、`app.js`(应用主模块)和`controllers`、`services`等子目录下的文件。 通过学习这个示例,你可以了解到...

    精通angularjs pdf 和 源码

    在大型应用中,路由懒加载和模块拆分有助于优化性能,只在需要时加载相关的代码模块。 **源码分析** 随书源码提供了丰富的示例,涵盖了上述各个知识点的实际应用,可以帮助读者更好地理解AngularJS的运作机制,并...

    AngularJS详细使用说明从零到高

    - **模块化**:AngularJS支持将应用程序划分为多个模块,每个模块可以有自己的控制器、服务和其他资源,便于管理和维护大型项目。 - **路由**:AngularJS内置了路由功能,可以实现SPA(单页面应用)中的页面跳转和...

    angularjs1.3

    6. **ngRepeat** 的优化:对于大型数据集,ngRepeat的性能得到了提升,通过惰性初始化和更有效的DOM操作减少了内存占用和渲染时间。 7. **UI-Router**:UI-Router是AngularJS的一个第三方路由插件,它提供状态管理...

    angularjs1.4js类库

    1. **ngRepeat优化**:ngRepeat指令是AngularJS中用于循环渲染元素的主力,1.4版本对它进行了性能优化,尤其是在处理大型数据集时,能更快地完成渲染。 2. **ControllerAs语法**:为了更好地遵循面向对象编程的原则...

    angularjs+boorstrap视频教程

    - **性能瓶颈**:针对大型应用可能出现的性能问题,可以通过懒加载、虚拟滚动等技术进行优化。 通过上述知识点的学习,开发者可以更好地掌握AngularJS和Bootstrap的核心技术和应用场景,为实际项目的开发打下坚实...

    AngularJS 1.5.8 api

    AngularJS遵循Model-View-Controller设计模式,提供了构建大型应用的良好结构。 总之,AngularJS 1.5.8 API提供了丰富的功能,帮助开发者构建复杂的单页应用程序。了解并熟练掌握这些核心概念和API,将极大地提高...

    angularjs 1.4.8 1.5.0 1.6.0 1.6.7完整包

    这个版本加强了模块化,使得构建大型应用更加容易管理。`ngComponentRouter`的引入使得在AngularJS 1.x中可以使用类似于Angular 2的路由方式。此外,`ngIf`和`ngSwitch`指令的改进提供了更好的性能和更丰富的功能。 ...

    angularjs1.3.9官方下载包

    这个库在处理大型应用中的导航和页面状态管理时非常有用。 AngularJS 1.3.x系列引入了许多改进和新特性,以下是一些关键点: 1. **ngAnimate**: 在1.3.9版本中,ngAnimate模块得到了增强,提供了更多的动画钩子,...

    AngularJS 中文版

    通过以上内容的学习,开发者不仅可以掌握AngularJS的基本概念和技术要点,还能够构建出高性能的Web应用程序,并且能够在任何浏览器中运行。此外,通过对AngularJS的深入了解,还可以更好地评估其与其他JavaScript...

    AngularJS与RequireJS集成

    集成AngularJS和RequireJS能带来更好的代码管理、优化加载性能和提升应用的可扩展性。然而,这也需要开发者对两者都有深入的理解,以便有效地利用它们的优势。在实际开发中,需要根据项目的具体需求来决定是否采用...

Global site tag (gtag.js) - Google Analytics