当前位置: 移动技术网 > IT编程>脚本编程>AngularJs > 再谈Angular4 脏值检测(性能优化)

再谈Angular4 脏值检测(性能优化)

2018年04月25日  | 移动技术网IT编程  | 我要评论

summary

angular 4的脏值检测是个老话题了,而理解这个模型是做angular性能优化的基础。因此,今天我们再来聊聊angular 4脏值检测的原理,并看看性能优化的小提示。

进入点 - zone.js

angular 4是一个mvvm框架。数据模型(model)转换成视图模型(viewmodel)后,绑定到视图(view)上渲染成肉眼可见的页面。因此,发现数据模型变化的时间点是更新页面的关键,也是调用脏值检测的关键。

经过分析,工程师们发现,数据的变化往往由macrotask和microtask等异步事件引起。因此,通过重写浏览器所有的异步api,就能从源头有效地监听数据变化。zone.js就是这样一个猴子脚本(monkey patch)。angular 4使用了一个定制化的zone(ngzone),它会通知angular可能有数据变化,需要更新视图中的数据(脏值检测)。

脏值检测(change detection)

脏值检测的基本原理是存储旧数值,并在进行检测时,把当前时刻的新值和旧值比对。若相等则没有变化,反之则检测到变化,需要更新视图。

angular 4把页面切分成若干个component(组件),组成一棵组件树。进入脏值检测后,从根组件自顶向下进行检测。angular有两种策略:default和onpush。它们配置在组件上,决定脏值检测过程中不同的行为。

default - 缺省策略

changedetectionstrategy.default。它还意味着一旦发生可能有数据变化的事件,就总是检测这个组件。

脏值检测的操作基本上可以理解为以下几步。1)更新子组件绑定的properties,2)调用子组件的ngdocheck和ngonchanges生命周期钩子(lifecycle hook),3)更新自己的dom,4)对子组件脏值检测。这是一个从根组件开始的递归方程。

// this is not angular code
function changedetection(component) {
 updateproperties(component.children);
 component.children.foreach(child => {
  child.ngdocheck();
  child.ngonchanges();
 };
 updatedom(component);
 component.children.foreach(child => changedetection(child));
}

我们开发者会非常关注dom更新的顺序,以及调用ngdocheck和ngonchanges的顺序。可以发现:

  1. dom更新是深度优先的
  2. ngdocheck和ngonchanges并不是(也不是深度优先)

onpush - 单次检测策略

changedetectionstrategy.onpush。只在input properties变化(onpush)时才检测这个组件。因此当input不变时,它只在初始化时检测,也叫单次检测。它的其他行为和default保持一致。

需要注意的是,onpush只检测input的引用。input对象的属性变化并不会触发当前组件的脏值检测。

虽然onpush策略提高了性能,但也是bug的高发地点。解决方案往往是将input转化成immutable的形式,强制input的引用改变。

tips

数据绑定

angular有3种合法的数据绑定方式,但它们的性能是不一样的。

直接绑定数据

<ul>
 <li *ngfor="let item of arr">
  <span>name {{item.name}}</span>
  <span>classes {{item.classes}}</span><!-- binding a data directly. -->
 </li>
</ul>

大多数情况下,这都是性能最好的方式。

绑定一个function调用结果

<ul>
 <li *ngfor="let item of arr">
  <span>name {{item.name}}</span>
  <span>classes {{classes(item)}}</span><!-- binding an attribute to a method. the classes would be called in every change detection cycle -->
 </li>
</ul>

在每个脏值检测过程中,classes方程都要被调用一遍。设想用户正在滚动页面,多个macrotask产生,每个macrotask都至少进行一次脏值检测。如果没有特殊需求,应尽量避免这种使用方式。

绑定数据+pipe

<ul>
 <li *ngfor="let item of instructorlist">
  <span>name {{item.name}}</span>
  <span>classes {{item | classpipe}}</span><!-- binding data with a pipe -->
 </li>
</ul>

它和绑定function类似,每次脏值检测classpipe都会被调用。不过angular给pipe做了优化,加了缓存,如果item和上次相等,则直接返回结果。

ngfor

多数情况下,ngfor应该伴随trackby方程使用。否则,每次脏值检测过程中,ngfor会把列表里每一项都执行更新dom操作。

@component({
 selector: 'my-app',
 template: `
  <ul>
   <li *ngfor="let item of collection;trackby: trackbyfn">{{item.id}}</li>
  </ul>
  <button (click)="getitems()">refresh items</button>
 `,
})
export class app {
 collection;
 constructor() {
  this.collection = [{id: 1}, {id: 2}, {id: 3}];
 }
  
 getitems() {
  this.collection = this.getitemsfromserver();
 }
  
 getitemsfromserver() {
  return [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
 }
  
 trackbyfn(index, item) {
  return index;
 }
}

reference

  1. he who thinks change detection is depth-first and he who thinks it's breadth-first are both usually right
  2. angular runtime performance guide

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网