再谈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 相关文章推荐
javascript 时间比较实现代码
Oct 28 Javascript
JS中confirm,alert,prompt函数使用区别分析
Apr 01 Javascript
jquery使用ajax实现微信自动回复插件
Apr 28 Javascript
iframe调用父页面函数示例详解
Jul 17 Javascript
jquery结婚电子请柬特效源码分享
Aug 21 Javascript
详解AngularJS如何实现跨域请求
Aug 22 Javascript
jqgrid实现简单的单行编辑功能
Sep 30 Javascript
JavaScript门道之标准库
May 26 Javascript
vue 实现数字滚动增加效果的实例代码
Jul 06 Javascript
vue 本地环境跨域请求proxyTable的方法
Sep 19 Javascript
使用eslint和githooks统一前端风格的技巧
Jul 29 Javascript
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
Jan 05 Vue.js
微信小程序之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
一个程序下载的管理程序(四)
2006/10/09 PHP
php强制用户转向www域名的方法
2015/06/19 PHP
PHP排序算法之简单选择排序(Simple Selection Sort)实例分析
2018/04/20 PHP
阿里对象存储OSS在laravel框架中的使用方法
2019/10/13 PHP
jQuery 添加/移除CSS类实现代码
2010/02/11 Javascript
关于jQuery UI 使用心得及技巧
2012/10/10 Javascript
js 利用image对象实现图片的预加载提高访问速度
2013/03/29 Javascript
js改变img标签的src属性在IE下没反应的解决方法
2013/07/23 Javascript
JQuery判断HTML元素是否存在的两种解决方法
2013/12/26 Javascript
JavaScript学习笔记之JS事件对象
2015/01/22 Javascript
JavaScript实现添加及删除事件的方法小结
2015/08/04 Javascript
bootstrap下拉列表与输入框组结合的样式调整
2016/10/08 Javascript
js常用的继承--组合式继承
2017/03/06 Javascript
微信小程序 动态绑定数据及动态事件处理
2017/03/14 Javascript
vue 本地服务不能被外部IP访问的完美解决方法
2018/10/29 Javascript
JavaScript对象拷贝与赋值操作实例分析
2018/12/10 Javascript
layui加载表格,绑定新增,编辑删除,查看按钮事件的例子
2019/09/06 Javascript
基于layui的table插件进行复选框联动功能的实现方法
2019/09/19 Javascript
基于vue3.0.1beta搭建仿京东的电商H5项目
2020/05/06 Javascript
[49:13]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第一局
2016/02/27 DOTA
给Python初学者的一些编程技巧
2015/04/03 Python
Python验证企业工商注册码
2015/10/25 Python
Python数据类型之String字符串实例详解
2019/05/08 Python
Tensorflow中tf.ConfigProto()的用法详解
2020/02/06 Python
tensorflow实现将ckpt转pb文件的方法
2020/04/22 Python
python读取hdfs上的parquet文件方式
2020/06/06 Python
Pandas替换及部分替换(replace)实现流程详解
2020/10/12 Python
HTML5 Canvas基本线条绘制的实例教程
2016/03/17 HTML / CSS
现代家居用品及礼品:LBC Modern
2018/06/24 全球购物
医院护理人员的自我评价分享
2013/10/04 职场文书
日语专业个人的求职信
2013/12/03 职场文书
个人简历中自我评价
2014/02/11 职场文书
中班上学期个人总结
2015/02/12 职场文书
2016年十一促销广告语
2016/01/28 职场文书
2016见义勇为事迹材料汇总
2016/03/01 职场文书
Python中glob库实现文件名的匹配
2021/06/18 Python