优化Angular应用的性能

359 查看

优化Angular应用的性能

MVVM框架的性能,其实就取决于几个因素:

  • 监控的个数
  • 数据变更检测与绑定的方式
  • 索引的性能
  • 数据的大小
  • 数据的结构

我们要优化Angular项目的性能,也需要从这几个方面入手。

1. 减少监控值的个数

监控值的个数怎么减少呢?

考虑极端情况,在不引入Angular的时候,监控的个数是为0的,每当我们有需要绑定的数据项,就产生了监控值。

我们注意到,Angular里面使用了一种HTML模板语法来做绑定,开发业务项目非常方便,但考虑一下,这种所谓的“模板”,其实与我们常见的那种模板是不同的。

传统的模板,是静态模板,将数据代入模板之后生成界面,之后数据再有变化,界面也不会变。但Angular的这种“模板”是动态的,当界面生成完毕,数据产生变更的时候,界面还是会更新。

这是Angular的优势,但我们有时候也会因为使用不当,反而增加困扰。因为Angular采用了变动检测的方式来跟踪数据的变化,这些事情都是有负担的,很多时候,有些数据在初始化之后就不再会变化,但因为我们没有把它们区分出来,Angular还是要生成一个监听器来跟踪这部分数据的变化,性能也就受到牵累。

在这种情况下,可以采用单次绑定,仅在初始化的时候把这些数据绑定,语法如下:

这样的数据就不会被持续观测,也就有效减少了监控值的数目,提高了性能。

2. 降低数据比对的开销

这一个环节是从数据变更检测与绑定的方式入手。细节不说太多了,之前都说过。从数据到界面的更新,一般就两种方式:推、拉。

所谓推,就是在set的时候,主动把与之相关的数据更新,大部分框架是这种方式,低版本浏览器用defineSetter之类。

所谓拉,就是set的时候只改变自己,关联数据等到用的时候自己去取。比如:

有些框架中,两种方式都可以用。这时候可以自己考虑下适合用哪种方式,比如说,可能有些框架是合并变更,批量更新的,可能就用拉的方式效率高;有些框架是实时变动,差异更新的,那可能就是用推的效率高些。

上面的代码能看出来,从代码编写的简洁性来说,拉模式要比推模式简单很多,如果能预知数据量较小,可以这样用。

在实际开发过程中,这两种方式是需要权衡的。我们举的这个例子比较简单,如果说某个属性依赖于很多东西,例如,一个很大的购物列表,有个总价,它是由每个商品的单价乘以购买个数,再累加起来的。

在这种情况下,如果使用拉模式,也就是在总价的get上做这个变动,它需要遍历整个数组,重新作计算。但是如果使用推模式,每次有商品价格或者商品购买个数发生变更的时候,都只要在原先的总价上,减去两次变动的差价即可。

此外,不同的框架用不同方式来检测数据的变动,比如Angular,如果有一个数组中的元素发生变化了,它是怎样知道这个数组变了呢?

它需要保持变动之前的数据,然后作比对:

  • 首先比对数组的引用是否相等,这一步是为了检测数组的整体赋值,比如this.arr = [1, 2, 3]; 直接把原来的替换掉了,如果出现这种情况,就认为它肯定变化了。(其实,如果内容与原先相同,是可以认为没有变的,但因为这些框架的内部实现,往往都需要更新数据与DOM元素的索引关系,所以不能这样)
  • 其次,比较数组的长度,如果长度跟原先不相等了,那肯定也产生变化了
  • 然后只能挨个去比对里面元素的变化了

所以,会有人考虑在Angular中结合immutable这样的东西,加速变更的判定过程,因为immutable的数据只要发生任何变化,其引用都一定会变,所以只要第一步判定引用就足以知道数据是否改变了。

有人说,你这个判定降低的开销并不大啊,因为引入immutable要增加复制的开销,跟这里的新旧数据比对开销相比,也低不到哪里去。但这个地方要注意,Angular在有事件产生的时候,会把所有监控数据都重新比对,也就是说,如果你在界面上有个大数组,你从未对它重新赋值,而是经常在另外一个很小的表单项绑定的数据上进行更新,这个数组也是要被比对的,这就比较坑了,所以如果引入immutable,可以大幅降低平时这种不受影响时候的比对成本。

但是引入immutable也会对整个应用造成影响,需要在每个赋值取值的地方都使用immutable的封装方式,而且还要在绑定的时候,对数据作解包,因为Angular绑定的数据是pojo。

所以,用这种方式还是要慎重,除非框架自身就构建在immutable的基础上。或许,我们可以期望有一套与ng-model平行的机制,ng-immutable之类,实现的难度也还是挺大的。

在使用ES5的场景下,可以利用一些方法加速判断,比如数组的:

  • filter
  • map
  • reduce

它们能够返回一个全新的数组,与原先的引用不等,所以在第一步判断就可以得出结果,不必继续后面几步的比较。

不过,这个环节的优化其实很不明显,最关键的优化在于与之配套的索引优化,参见下一节。

3. 提升索引的性能

在Angular中,可以通过ng-repeat来实现对数组或者对象的遍历,但这个遍历的机制,其实有很多技巧。

在使用简单类型数组的时候,我们很可能会碰到这么一个问题:数组中存在相同的值,比如: