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


Posted in Javascript onApril 23, 2018

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

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

Javascript 相关文章推荐
在你的网页中嵌入外部网页的方法
Apr 02 Javascript
javascript 单例/单体模式(Singleton)
Apr 07 Javascript
jQuery制作仿腾讯web qq用户体验桌面
Aug 20 Javascript
js实现瀑布流的一种简单方法实例分享
Nov 04 Javascript
Javascript基础教程之switch语句
Jan 18 Javascript
JavaScript实现为指定对象添加多个事件处理程序的方法
Apr 17 Javascript
javascript实现简单加载随机色方块
Dec 25 Javascript
JavaScript仿淘宝页面图片滚动加载及刷新回顶部的方法解析
May 24 Javascript
jQuery中table数据的值拷贝和拆分
Mar 19 Javascript
react中使用swiper的具体方法
May 15 Javascript
微信小程序内拖动图片实现移动、放大、旋转的方法
Sep 04 Javascript
原生JS实现动态加载js文件并在加载成功后执行回调函数的方法
Dec 30 Javascript
微信小程序之swiper轮播图中的图片自适应高度的方法
Apr 23 #Javascript
vue.js中实现登录控制的方法示例
Apr 23 #Javascript
JS中获取 DOM 元素的绝对位置实例详解
Apr 23 #Javascript
Vue前端开发规范整理(推荐)
Apr 23 #Javascript
Vue 中mixin 的用法详解
Apr 23 #Javascript
详解Vue2.0配置mint-ui踩过的那些坑
Apr 23 #Javascript
vue2.0 移动端实现下拉刷新和上拉加载更多的示例
Apr 23 #Javascript
You might like
CodeIgniter表单验证方法实例详解
2016/03/03 PHP
php实现批量删除挂马文件及批量替换页面内容完整实例
2016/07/08 PHP
详解在YII2框架中使用UEditor编辑器发布文章
2018/11/02 PHP
YII2框架中添加自定义模块的方法实例分析
2020/03/18 PHP
[原创]js获取数组任意个不重复的随机数组元素
2010/03/15 Javascript
js中parseFloat(参数1,参数2)定义和用法及注意事项
2013/01/27 Javascript
加载远程图片时,经常因为缓存而得不到更新的解决方法(分享)
2013/06/26 Javascript
JS中如何判断传过来的JSON数据中是否存在某字段
2014/08/18 Javascript
nodejs中的fiber(纤程)库详解
2015/03/24 NodeJs
js数组去重的5种算法实现
2015/11/04 Javascript
JavaScript获取各大浏览器信息图示
2015/11/20 Javascript
js实现三级联动效果(简单易懂)
2017/03/27 Javascript
对vue中v-on绑定自定事件的实例讲解
2018/09/06 Javascript
python基础教程之Hello World!
2014/08/29 Python
python下setuptools的安装详解及No module named setuptools的解决方法
2017/07/06 Python
老生常谈进程线程协程那些事儿
2017/07/24 Python
微信跳一跳游戏python脚本
2020/04/01 Python
python实现求特征选择的信息增益
2018/12/18 Python
Python企业编码生成系统之系统主要函数设计详解
2019/07/26 Python
浅谈PyTorch的可重复性问题(如何使实验结果可复现)
2020/02/20 Python
Python Tornado之跨域请求与Options请求方式
2020/03/28 Python
解决keras GAN训练是loss不发生变化,accuracy一直为0.5的问题
2020/07/02 Python
悦木之源美国官网:Origins美国
2016/08/01 全球购物
医学生自我鉴定范文
2013/11/08 职场文书
应届护士推荐信
2013/11/16 职场文书
2014年大班元旦活动方案
2014/02/26 职场文书
入股协议书范本
2014/04/14 职场文书
垃圾桶标语
2014/06/24 职场文书
2014年宣传工作总结
2014/11/18 职场文书
2014年效能监察工作总结
2014/11/21 职场文书
房地产销售助理岗位职责
2015/04/14 职场文书
《惊弓之鸟》教学反思
2016/02/20 职场文书
2016年社区六一儿童节活动总结
2016/04/06 职场文书
pytorch DataLoader的num_workers参数与设置大小详解
2021/05/28 Python
使用 MybatisPlus 连接 SqlServer 数据库解决 OFFSET 分页问题
2022/04/22 SQL Server
分享很少见很有用的SQL功能CORRESPONDING
2022/08/05 MySQL