欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-04 17:04:09
summary angular 4的脏值检测是个老话题了,而理解这个模型是做angular性能优化的基础。因此,今天我们再来聊聊angular 4脏值检测的原理,并看...

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

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