什么是变更检测?下面本篇文章带大家了解一下angular中的变更检测机制,聊聊变更检测是如何工作的,并介绍一下angular变更检测的性能优化方法,希望对大家有所帮助!
什么是变更检测(change detection)?变更检测的概念组件内的数据状态变化以后,需要对应更新视图。这种将视图和数据同步的机制,就叫变化检测。【相关教程推荐:《angular教程》】
变更检测的触发时机只要发生了异步操作(events, timer, xhr),angular 就会认为有状态可能发生变化了,然后就会进行变更检测。
events::click,mouseover,mouseout,keyup,keydown 等浏览器事件;timer:settimeout/setinterval;xhr:各类请求等。既然都是对异步操作进行变更检测,那么angular是如何订阅异步请求,进行变更检测的呢?
这里介绍下ngzone以及它的fork对象zone.js。
zone.js 用于封装和拦截浏览器中的异步活动、它还提供 异步生命周期的钩子 和 统一的异步错误处理机制。
zone.js 是通过 monkey patching(猴子补丁) 的方式来对浏览器中的常见方法和元素进行拦截,例如 settimeout 和 htmlelement.prototype.onclick。angular 在启动时会利用 zone.js 修补几个低级浏览器 api,从而实现异步事件的捕获,并在捕获时间后调用变更检测。
angular通过forkzone.js并拓展出一个自己的区域ngzone,让应用中的所有异步操作都会运行在这个区域中。
angular的变更检测如何工作的?angualr会为每一个组件生成一个变化监测器changedetector ,记录组件的变化状态。
我们在创建了一个angular 应用后,angular 会同时创建一个 applicationref 的实例,这个实例代表的就是我们当前创建的这个 angular 应用的实例。 applicationref 创建的同时,会订阅 ngzone 中的 onmicrotaskempty 事件,在所有的微任务完成后调用所有的视图的detectchanges()来执行变更检测。
变更检测的执行顺序更新所有子子组件绑定的属性
调用所有子组件生命周期的钩子 onchanges, oninit, docheck,aftercontentinit
更新当前组件的dom
调用子组件的变更检测
调用所有子组件的生命周期钩子 ngafterviewinit
举个栗子,我们在开发模式的时候可能会遇到这种报错:
这是由于变更检测遵循从根组件开始,从上到下,执行每个组件的变更检测,直到最后一个组件达到稳定状态。而在下一次变更检测之前,子孙组件都不允许去修改父组件里的属性。
情况1 在开发模式下,angular会进行二次检测 (生产环境下调用enableprodmode(),检测次数会降为1)。一旦我们在 第4步 完成后,在子孙组件里修改父组件的属性,那么,angular在执行第二次检测的时候,发现两次的值不一致,就会出现上述错误。
情况2 只要父组件对子组件做了属性绑定,那么不管是在onchanges,oninit,docheck,aftercontentinit 和 afterviewinit 中的任意一个生命周期钩子中执行下述代码,也会报错。
// #parent{{data}}<child [data]="data"></child>// in child component ts, execute:this.parent.data = 'new value';
变更检测的执行策略default 策略
每次事件触发变化检测(如用户事件、计时器、xhr、promise 等)时,此默认策略都会从上到下检查组件树中的每个组件。这种对组件的依赖关系不做任何假设的保守检查方式称为 脏检查,这种策略在我们应用组件过多时会对我们的应用产生性能的影响。
onpush 策略
修改组件装饰器的changedetection,设置为 onpush 策略后,angular 每次触发变化检测后会跳过该组件和该组件的所以子组件变化检测。
在 onpush 策略下,只有以下这几种情况才会触发组件的变化检测:
输入值(@input)更改(入input的值必须是一个新的引用)当前组件或子组件之一触发了事件 (但在onpush策略中,以下操作不会触发变更检测)settimeout()setinterval()promise.resolve().then()this.http.get('...').subscribe()手动触发变更检测(每个组件都会关联一个组件视图changedetectorref)detectchanges(): 它会触发当前组件和子组件的变化检测markforcheck(): 它不会触发变化检测,但是会把当前的onpush组件和所以的父组件为onpush的组件 标记为需要检测状态 ,在当前或者下一个变化检测周期进行检测applicationref.tick() : 它会根据组件的变化检测策略,触发整个应用程序的更改检测async pipe对于angular的变更检测如何优化?由于组件默认执行 default策略 ,任何异步操作都会触发整个组件数从上到下的检查。即使angular团队不断提升性能,可以在毫秒内完成上百次检测,但是当应用拓展至百上千个组件组成时,庞大的组件树对应的变更检测也会达到性能瓶颈。
此时,我们就需要开始分析并减少不必要的检测次数。
如何减少检测次数区域污染(zone pollution)
一般我们在生命周期钩子里使用第三方库,比如chart类库初始化,会自带requestanimationrequest/settimeout/addeventlistener,我们可以将初始化方法写入ngzone的runoutsideangular方法中。
onpush 策略
对于不涉及更新操作的视图可以剥离出组件,使用onpush策略,以通知更新的方式刷新视图(见上方 变更检测的执行策略 部分)。
用 pure pipe 代替 {{function(data)}}
在html文件内,{{function(data)}}的写法会导致每次变更检测发生时,所有数值都会重新被计算。(?:当一个1000条的列表,你只修改了其中一条数据,但另外另外999条无需更新的数据也会被重新运算。)
此时,我们可以使用pipe的方式,只有变更的值会触发运算,更新部分视图。
面对大量数据的渲染,选择虚拟滚动/分页请求数据
以上4点解决方案,来源于angular团队的视频介绍,视频中以angular devtool运算次数,来分析问题解决问题。所以,如果你的angular是9+,请继续看下去吧,如何安装angular devtool和运行。
插件:angular devtool使用介绍angular 9+, 支持ivy。guide和下载地址保证运行环境为开发环境// environment.dev.ts... production: false...
angular.json > dev配置项 > "optimization": falseprojects > your-project-name > architect > build > configurations > dev > "optimization": false
更多编程相关知识,请访问:编程教学!!
以上就是浅析angular变更检测机制,聊聊如何进行性能优化?的详细内容。