angular 性能优化——变更检测
对于前端性能指标描述,业界都各有说词,总结下来都和首屏性能和页面流畅度相关, 本次将会从页面流畅度的角度,对页面交互性能优化进行分析。【相关教程推荐:《angular教程》】
什么是页面流畅度?
页面流畅度是通过帧率 FPS(Frames Per Second - 每秒传输帧数)判定的,一般主流的浏览器屏幕刷新率都在 60Hz(每秒刷新60次),最优的帧率在 60 FPS,帧率越高,页面就越流畅,60Hz意味着每隔16.6ms会刷新一次显示屏,也就是每一次渲染页面需要在16.6ms内完成,否则就会导致页面失帧,出现卡顿现象。根因在于:浏览器中的 JavaScript 执行和页面渲染会相互阻塞
。
在 Chrome 的 devtools 中我们可以执行 Cmd+Shift+P 输入 show fps 来快速打开 fps 面板,如下图所示:
通过观察 FPS 面板,我们可以很方便的对当前页面的流畅度进行监控
1 影响页面性能的因素
页面交互是否流畅,在于页面响应是否流畅,而页面响应其本质上就是把页面状态的变更重新渲染到页面上的过程。
页面响应过程大致如下:
一般情况Event Handler事件处理逻辑不会消耗太多时间,所以影响angular性能的因素主要在于异步事件触发
和变更检测
。
一般情况Event Handler事件处理逻辑不会消耗太多时间,所以影响angular性能的因素主要在于异步事件触发和变更检测。
对angular来说,页面渲染的过程就是变更检测的过程,可以理解为angular的变更检测要在16.6ms内完成才不会导致页面失帧、卡顿。
可以从以下三方面优化页面响应的性能。
(1)对于触发事件阶段,可以减少异步事件的触发,来减少整体的变更检测次数和重新渲染;
(2)对于 Event Handler 执行逻辑阶段,可以通过优化复杂代码逻辑来减少执行时间;
(3)对于 Change Detection 检测数据绑定并更新 DOM 阶段,可以减少变更检测和模板数据的计算次数来减少渲染时间;
对于(2)Event Handler要具体问题具体分析,不做讨论,主要针对(1)(3)进行优化
2 优化方案
2.1 减少异步事件触发
Angular在默认变更检测模式下,异步事件会触发全局的变更检测,因此,减少异步事件的触发会大大的提升angular的性能。
异步事件包括Macro Task(宏任务)事件和Micro Task微任务事件
对异步事件的优化主要是针对document的监听事件。比如document上的click、mouseup、mousemove…等监听事件。
监听事件场景:
Renderer2.listen(document, …)
fromEvent(document,…)
document.addEventListener(…)
dom监听事件,在不需要触发的时候一定要移除。
举例:[pop]提示框指令
使用场景:表格列筛选,点击图标以外的地方,或者页面滚动,列筛选弹框隐藏
以前的做法是直接在pop指令里监听document的click事件和scroll事件,这样有个弊端就是提示框未显示,但依然存在监听事件,很不合理。
合理的解决方案:当提示框显示的时候才去监听click和scroll事件,隐藏的时候就移除监听事件。
对于频繁触发的dom监听事件,可以使用rjx的操作符对事件进行优化。详情参考Rjx操作符。RxJS Marbles。
2.2 变更检测
什么是变更检测?
要理解变更检测,我们可以从变更检测的目标寻找答案。angular变更检测目标,是让模型(TypeScript代码)与模板(HTML)保持同步。因此,变更检测可以理解为:检测模型变更的同时,更新模板( DOM ) 。
变更检测流程是什么?
通过在组件树中按照自顶向下的顺序执行变更检测,也就是先对父组件执行变更检测,再对子组件进行变更检测。首先检查父组件的数据变更,然后更新父组件模板,在更新模板的时候遇到子组件,会去更新子组件上绑定的值,然后进入子组件,看@Input输入值的索引是否改变,如果改变就将该子组件标记为dirty,也就是后续需要变更检测的,标记完子组件之后,继续更新父组件中子组件后面的模板,父组件模板全部更新完之后再去对子组件做变更检测。
2.2.1 angular变更检测原理
在默认变更检测default模式下,异步事件触发Angular的变更检测的原理是 angular通过使用Zone.js处理异步事件时调用了ApplicationRef 的tick()方法从根组件到子组件执行变更检测。 Angular 应用初始化过程中,实例化了一个zone (NgZone),然后将所有逻辑都跑在该对象的 _inner 对象中。
Zone.js实现了以下几个类:
- Zone类,JavaScript 事件的执行环境,和线程一样,它们可以带一些数据,并且可能拥有父子 zone。
- ZoneTask类,包装后的异步事件,这些 task 有三种子类:
- MicroTask,由 Promise 创建。
- MacroTask,由 setTimeout 等创建。
- EventTask,由 addEventListener 等创建,比如dom事件。
- ZoneSpec对象,创建一个 ngZone 时给它提供的参数,有三个可以触发检测的钩子:
- onInvoke,调用某个回调函数时触发的钩子。
- onInvokeTask,ZoneTask 被触发时触发的钩子,比如 setTimeout 到时。
- onHasTask,检测到有或无 ZoneTask 时触发的钩子(即对第一个 schedule 的 zone 和最后一个 invoke 或 cancel 的 task 触发)。
- ZoneDelegate类,负责调用钩子。
检测过程原理大概如下:
用户操作触发异步事件(比如:dom事件,接口请求…)
=> ZoneTask类处理事件。invokeTask()函数中调用zone的runTask()方法,在runTask方法中,zone通过_zoneDelegate实例属性,调用ZoneSpec的钩子
=> ZoneSpec的三个钩子(onInvokeTask、onInvoke、onHasTask)钩子里通过checkStable()函数触发zone.onMicrotaskEmpty.emit(null)通知
=> 根组件监听onMicrotaskEmpty后调用tick(),tick方法中调用 detectChanges()
从根组件开始检测
=> ··· refreshView()
调用executeTemplate()
,executeTemplate
方法中调用templateFn()
更新模板、子组件绑定的值(这时候会去检测子组件的
@Input()输入引用是否改变,如果有改变,会将子组件标记为
Dirty,也就是该子组件需要变更检测
)
详细变更检测原理流程图:
简化流程:
触发异步事件
=> ZoneTask处理事件
=> ZoneDelegate 调用ZoneSpec的钩子触发onMicrotaskEmpty通知
=> 根组件收到onMicrotaskEmpty通知,执行tick(),开始检测并更新dom
由以上代码可知,当微任务为空的时候才会触发变更检测
。
简略变更检测原理流程图:
Angular 源码解析 Zone.js参考blog。
2.2.2 变更检测优化方案
1 )使用OnPush 模式
原理:减少1次变更检测的耗时。
OnPush模式与Default模式的区别在于:dom监听事件、timer事件、promise都不会触发变更检测。Default模式的组件状态始终为CheckAlways,表示组件每次检测周期都要检测。
OnPush模式下:以下情况会触发变更检测
S1、组件的@Input引用发生变化。
S2、组件的DOM绑定的事件,包括它子组件的DOM绑定的事件,比如 click、submit、mouse down。@HostListener()
注意:
通过renderer2.listen()监听的dom事件不会触发变更检测
通过dom.addEventListener()原生监听方式也不会触发变更检测
S3、Observable 订阅事件,同时设置 Async pipe。
S4、利用以下方式手动触发变化检测:
ChangeDetectorRef.detectChanges():触发当前组件和非OnPush子组件的变更检测。
ChangeDetectorRef.markForCheck():将当前视图及其所有的祖先标记为脏,下次检测周期时候会触发检测。
ApplicationRef.tick():不会触发变更检测
2 )使用NgZone.runOutsideAngular()
原理:减少变更检测次数
将全局dom事件监听写在NgZone.runOutsideAngular()方法的回调里面,dom事件将不会触发angular的变更检测。如果当前组件未更新,可以在回调函数里执行ChangeDetectorRef的detectChanges()钩子来手动触发当前组件的变更检测。
举例:app-icon-react动态图标组件
2.2.3 调试方式
方式1:可以在浏览器控制台,使用Angular DevTools插件查看某一次dom事件,angular的检测情况:
方式2:可以在控制台直接输入:ng.profiler.timeChangeDetection()查看检测时间,这种方式可查看全局的检测时间。参考博客 Profiling Angular Change Detection
2.3 模板(HTML)优化
2.3.1 减少DOM渲染:ngFor加trackBy
使用 *ngFor 的 trackBy 属性,Angular 只更改和重新渲染已更改的条目,而不必重新加载整个条目列表。
比如:表格排序场景。ngFor如果加了trackBy,表格渲染的时候只会移动行dom元素,如果不加trackBy,会先删除现有的表格dom元素,再新增行dom元素。显然只移动dom元素性能会好很多。
2.3.2 模板表达式中不要使用函数
不要在Angular 模板表达式中使用函数调用,可以用管道pipe代替,也可以通过手动计算后用一个变量代替。模板中使用函数,不管值有没有改变,每次变更检测的时候都会执行函数,会影响性能。
模板中使用函数的场景:
2.3.3 减少ngFor的使用
使用ngFor,数据量大的时候会影响性能。
举例:
使用ngFor:
不使用ngFor:性能提升10倍左右
更多编程相关知识,请访问:编程视频!!
以上就是angular如何进行性能优化?变更检测方式浅析的详细内容,转载自php中文网
发表评论 取消回复