再谈Angular4 脏值检测(性能优化)
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的顺序。可以发现:
- dom更新是深度优先的
- 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
- he who thinks change detection is depth-first and he who thinks it's breadth-first are both usually right
- angular runtime performance guide
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: Vue 动态设置路由参数的案例分析
下一篇: 简单实现Ajax无刷新分页效果