宝哥软件园

组织使用AngularJS框架过程中的一些性能优化要点

编辑:宝哥软件园 来源:互联网 时间:2021-09-16

1.介绍

无论您是编写一个旧的应用程序,还是在大型应用程序中使用AngularJS,性能都是一个重要的方面。了解是什么导致AngularJS应用程序变慢是非常重要的,在开发过程中做出权衡也是非常重要的。本文将介绍AngularJS的一些常见性能问题,以及优化建议。

2.性能测试工具。

本文采用JSPerf http://jsperf.com/性能测试的基准。

3.软件性能。

评估软件性能有两个基本因素:

首先是算法的时间复杂度。一个简单的例子是,线性搜索和二分搜索法之间存在显著的性能差距。

软件缓慢的第二个原因叫做空间复杂性。这是计算机运行应用程序所需的“空间”或内存。你需要的内存越多,它运行的速度就越慢。

4 JavaScript的性能。

一些性能问题不仅仅是由Angular引起的,而是JavaScript固有的。

4.1循环。

避免在循环中调用函数,而是转移到外部调用。

var sum=0;for(var x=0;x 100x){ var keys=object . keys(obj);sum=sum key[x];}上面的方面显然没有下面的快:

var sum=0;变量键=对象键(对象);for(var x=0;x 100x){ sum=sum key[x];}4.2 DOM访问。

获取DOM元素时要注意。

棱角分明。元素(' div。元素类)非常昂贵。实际上,这不会在AngularJS中引起太多问题。但是关注一下就好了。DOM树应该小,DOM访问应该尽可能少。

4.3可变垃圾收集的范围。

尽可能严格地限制变量的范围,以便垃圾收集器可以更快地回收空间。请注意以下问题:

function demo(){ var b={ childfunction : function(){ console . log(' hi这是子函数')};b . ChildFuncTion();返回b;}

当此功能结束时,没有对B的引用。B将被回收。但如果有这样一句台词:

var cFunc=demo();该引用将阻止垃圾收集。尽量避免这样的引用。

4.4数组和对象。

这里有很多要点:

例如:

for(var x=0;xarr.lengthx ) { i=arr[x]。指数;}比这个快一点(注意* arr是数组,obj是json对象)。

for(var x=0;x100x ) { i=obj[x]。指数;}比这个快一点。

变量键=对象键(对象);for(var x=0;x keys.lengthx ){ i=obj[keys[x]]。指数;}5个重要概念。

我们已经讨论了JavaScript的性能,现在有必要看看AngualrJS中的核心概念,看看它是如何工作的。

5.1范围和摘要周期。

角度域本质上是从一些预定义对象继承而来的JavaScript对象。基本上,小域比大域运行得快。

换句话说,每次创建一个新的域,更多要回收的内容将被添加到垃圾收集器中。

编写AngularJS应用程序时应该特别注意的一个核心概念和性能影响是摘要周期。事实上,每个字段将存储一组方法$$watchers。

每当域或绑定DOM中的值(属性),如ng-repeat、ng-switch和ng-if,调用$watch时,一个函数将被添加到相应域中的$$watchers数组队列中。

当字段中的值改变时,将触发并调用$$watchers中的所有watchers函数。当它们中的任何一个修改域中的某个值时,它们将被触发再次执行。

这个过程将继续循环,直到在$$watcher数组的队列中没有任何更改或抛出异常。

此外,如果任何代码执行$scope。$apply(),将触发更新周期。

最后,$scope.evalAsync()将在异步调用中执行,其更新周期不会在当前和下一个执行周期中调用。

6.设计Angular时应遵守的一般准则。

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

这一切告诉我们什么?首先,我们应该尽可能简化我们的对象。当对象从服务器返回时,这一点尤其重要。

将数据库中的行直接转换为对象只是一种临时解决方案,因此不要使用。toJson()。

只需要返回Angular所需的属性值。

6.2监控功能(监视功能)。

另一个常见的问题是观察者的函数边界。不要捆绑任何东西(ng-show、ng-repeat等)。)直接转换为函数。不要直接监控任何函数的返回值。此功能将在每个更新周期中执行,这可能会降低应用程序的运行速度。

6.3监控对象(观察对象)。

同样,Angular提供了第三个可选参数来监视整个对象的变化。将调用$watch的第三个参数设置为true。这是一个非常可怕的想法。更好的解决方案是依靠服务和对象的引用来监控域之间的变化。

7个列表问题。

7.1长列表(列表)

有些人试图避免长名单。Ng-repeat会做一些繁重的DOM操作(更不用说污染$$watchers了),所以无论是在分页还是无限滚动中,都尽量使用小数据进行渲染。

7.2过滤器(过滤器)。

尽量避免使用过滤器。它们将在每个更新周期中运行两次,一次是在发生任何更改时,另一次是在收集更深入的更改时触发。所以不要直接从内部列表中移除对象,只需使用CSS控件即可。(注意*通过添加CSS类名隐藏它们)

渲染时,$index的值不是真正的数组索引值,所以没有价值。但是,有序的数组索引不能让您遍历列表中的所有字段。

7.3更新ng-重复。

使用ng-repeat时,尽量避免刷新全局列表。Ng-repeat将生成一个$$hashkey属性和一个系统唯一项。这意味着当您调用范围时。listboundtongrepeat=serverfetch(),整个列表将再次刷新。所有的观察者都会被通知执行,每个元素都会被触发,这非常消耗性能。

这里有两种解决方案。一种是用过滤器维护两个集合和ng-repeat(基本上需要定制同步逻辑,算法比较复杂,可维护性比较差),另一种是用track by指定自己的密钥(Angular 1.2开始支持,只需要少量的同步逻辑)。

简而言之:

scope . arr=mockServerFetch();它会比下面的慢。

var a=mockServerFetch();for(var I=scope . arr . length-1;I=0;i - ){ var result=_。find(a),function(r){ return(r . tracking key==scope . arr[I])。tracking key);});if(!结果){ scope.arr.splice(i,1);} else { a . splice(a . indexof(scope . arr[I]),1);} }_.map(a),function(NewItem){ scope . arr . push(NewItem);});这样的

div-repeat=' a . tracking key的a in arr track '比上面的慢。

Div ng-repeat='a in arr'8呈现问题。

Angular应用缓慢的另一个原因是不正确使用隐藏/显示或开关。

Ng-hide和ng-show只需切换CSS显示属性。这意味着看似看不见的东西依然存在于域中,所有的$ $观察者依然会被触发。

事实上,ng-if和ng-switch是从DOM中完全移除的,相应的字段也将被移除。性能差异明显。

9.更新周期。

9.1装订

尽量减少你的束缚。在Angular 1.3中,有一个新的一次性绑定语法{ { :scopeValue } }。它只由域执行一次,不会添加到观察器数组中。

9.2 $digest()和$apply()。

范围。$apply是一个强大的工具,允许您将外部值引入Angular。本质上,它会触发Angular的所有事件(如ng-click)。问题在于范围。$apply将从根域$rootScope开始,遍历所有域链,并触发每个域。

范围。$digest将只执行指定的域及其相关的域。两次表演的区别不言而喻。折衷方案是在下一个更新周期之前不触发任何域。

9.3美元手表()

范围。$watch()已经在很多场景中讨论过了。基本上,范围。$watch是糟糕设计的标志。如果你要创造一个观察者。记得尽量解开。您可以使用$watch的返回功能解除绑定。

var unbinder=作用域。$ watch(' scopeevaluetobewatcher ',函数(newVal,oldVal){ });unbinder();//这一行从$$watchers中删除观察器。如果不能提前解除绑定,请记住在$ on($ destroy)中解除绑定。

9.4美元on,$broadcast和$emit。

像$watch一样,它们是遍历整个范围的慢速事件。它们可能像GOTO一样,阻止您的程序调试。幸运的是,像$watch一样,它们可以在完全不需要的时候解开。例如,在$ on($ destroy)中。

950万美元销毁

如前所述,您应该解除$ on($ destroy)中所有事件侦听器的绑定,取消$timeout的任何实例,或者任何其他异步交互。这不仅仅是为了确保安全。它还允许你的域名更快地被回收。如果他们不这样做,他们将总是在后台运行。你直接清空CPU和RAM。

此外,在DOM上解除事件侦听器的绑定非常重要。否则,在旧浏览器中可能会导致内存泄漏。

9.6美元evalAsync

范围。$evalAsync是一个强大的工具。它可以在当前域中执行,不会触发域的更新。EvalAsync可以大大提高网页的性能。

10指令问题。

10.1隔离域(隔离范围)和转换。

域隔离和转换是Angular最令人兴奋的特性。它们是Angular的核心组件。

然而,也有一些权衡。指令不能直接创建字段来替换其父组元素。通过隔离域或Transclusion,我们可以创建一个新的对象来跟踪和添加一个新的监视器,但这也会降低应用程序的性能。添加之前仔细考虑一下这是否有必要。

10.2编译周期。

Directive的编译功能是在附加域之前操作DOM的完美功能(比如绑定事件)。一个重要的性能方面是传递到编译函数中的元素和属性在原始html模板中呈现。它只会运行一次,然后直接使用。另一个要点是链接前和链接后的区别。预链接从外到内执行。Postlinks是由内而外执行的。Prelink的性能稍好,因为它不会产生第二个更新周期。但是,此时还没有创建子元素的DOM。

11 DOM事件问题。

Angular提供了许多预定义的DOM事件指令。Ng-click、ng-mouseenter、ng-mouseleave等等。这些事件将在scole时执行。调用了$apply()。另一种更有效的方法是直接在DOM上绑定addEventListener,并尝试使用范围。$摘要。

优化实例

测试应用程序框架确实是一个严峻的挑战。当用户点击日志中的任何一个词,我们都会搜索相关信息,页面上可以点击的元素数不胜数。我们希望日志的分页功能能够获得即时反馈。其实我们已经提前获取了下一页的日志数据,所以用户界面的更新就成了瓶颈。如果用AngularJS直接实现日志视图的换页功能,需要1.2秒,但如果仔细优化可以减少到35毫秒。这些优化已经被证明适用于应用程序的其他部分,并且对AngularJS具有良好的适应性。但是我们必须打破一些规则来实现我们的想法,我们稍后会讨论它。

201635161723160.jpg  (590558)

Github更新的日志演示。

AngularJS日志查看器

本质上,日志视图是日志消息的列表,每个单词都可以被点击。因此,将Angular的指令添加到DOM元素中,简单实现如下:

Span class=' LogLine ' ng-repeat=' LogLine toshow ' Span class=' LogToken ' ng-repeat='第' {token | formatToken}行中的Token}} /span/Span单页应用程序中有数千个令牌是正常的。在早期的测试中,我们发现进入日志的下一页需要几秒钟来执行JavaScript。更糟糕的是,不相关的操作(如单击导航下拉框)不会被延迟。AngularJS大神说,数据元素绑定数量最好控制在200个以下。对我们来说,一个单词是一个元素,它已经超过了这个数字。

分析:

借助Chrome的JavaScript profiler工具,我们可以快速定位两个拖延点。首先,每次更新都需要花费大量时间来创建和销毁DOM元素。如果新视图有不同的行或任何一行有不同的字数,Angular ng-repeat指令将创建或销毁DOM元素,这代价太高。

其次,每个词都有自己的变化观察器,AngularJS会观察这些词,一旦鼠标点击就会触发,这是影响无关操作(下拉菜单导航)延迟的罪魁祸首。

优化#1:缓存DOM元素。

我们创造了ng-repeat指令的变体。在我们的版本中,如果绑定数据的数量减少,超出的DOM元素将被隐藏而不是销毁。如果以后元素的数量增加,我们将重用这些缓存的元素。

优化# 2:聚合观察器

用于呼叫变更观察者的大部分时间都被浪费了。在我们的应用程序中,特定单词上的数据绑定永远不会改变,除非整个日志消息发生变化。为了实现这一点,我们创建了一个指令“hides”来隐藏带有子元素的变更观察器,并且只有在特定父元素的表达式被修改时才会调用它们。这样,我们就避免了每次鼠标点击或其他微小修改所导致的总变化观察者(为了实现这个想法,我们稍微修改了AngularJS的抽象层,我们将在后面详细说明)。

优化#3:推迟元素创建。

如前所述,我们已经为日志中的每个单词分别创建了DOM,我们可以在每行中使用单个DOM元素来获得相同的视觉呈现。其他元素是响应鼠标点操作创建的,所以我们决定推迟这个部分的创建,只在鼠标移动到某一行的时候创建。

为了实现这一点,我们为每一行创建了两个版本,一个是简单的文本元素,用于显示完整的日志信息,另一个是占位符,用于显示每个单词最终填充后的效果。这个占位符最初是隐藏的,直到鼠标移动到该行时才会显示,而此时简单的文本行将被隐藏。我们将讨论占位符如何填充单词元素。

优化#4:避免监控隐藏元素。

我们创建了另一个指令来防止监视隐藏元素。该指令支持优化#1。与原始数据相比,我们有更多隐藏的DOM节点,因此我们必须消除对额外DOM节点的监控。这也支持优化#3,这使得推迟单词节点的创建更容易。在标记化版本出现之前,我们不会创建这一行数据。

以下代码均经过优化,我们的定制说明以粗体显示。

span class=' LogLine ' sly-repeat=' LogLine '中的line to show ' sly-evaluate-only-when=' LogLine ' div-mouse enter=' mouse hasered=true ' span ng-show='!mousehaseintered“{ logLine | format line } }/span Div ng-show=' mousehaseintered ' sly-prevent-evaluation-when-hidden span class=' logToken ' sly-repeat='行中的标记“{ token | format token } }/span/Div/Div/span sly-repeat是ng-repeat的变体,用于隐藏额外的DOM元素,而不是销毁它们。sly-evaluate-only-当阻止内部变更观察者时。除非“log”变量被修改,否则当鼠标移动到指定行的顶部时,sly-prevent-evaluate-when-hidden主要负责显示隐藏的div。

这显示了AngularJS对封装和分离的控制。我们进行了复杂的优化,但并没有影响模板的结构(这里显示的代码不是真实产品中的代码,但它显示了所有的关键点)。

结果:

我们来看看效果。我们添加了一些代码来测量它,从鼠标点击到Angular的$digest循环结束(这意味着DOM更新结束)。

我们通过Tomcat日志来衡量点击“下一步”按钮的性能,环境使用MacBook Pro上的Chrome。结果如下表所示(每个数据是10次测试的平均值):数据已经缓存,数据是从服务器获取的。简单实现1190 ms 1300 ms,优化后35 ms 201 ms。这些数据不包括浏览器在DOM布局和重绘(JavaScript执行完成后)所花费的时间,每次大约30 ms。尽管如此,效果还是很明显的。下一页的响应时间从1200毫秒下降到35毫秒(如果包括渲染,则为65毫秒)。

“从服务器获取数据”中的数据包括我们使用AJAX从后端获取日志数据的时间。这与单击“下一步”按钮不同,因为我们预先设置了下一页的日志数据,但它可能适用于其他UI响应。即便如此,优化后的程序也可以实时更新。

更多资讯
游戏推荐
更多+