当前位置: 首页 > Web前端 > HTML5

找到真正的罪魁祸首EchartsorAngular

时间:2023-04-05 14:27:59 HTML5

找到真正的罪魁祸首EchartsorAngular这是一个故事,就像技术一样,我们追求的不是一个结局,而是一个启发和深刻共鸣的过程,那就是我们的成长经历生产力的积累!故事从打开“疯狂”的ionic3应用页面开始,什么也没做。5s的angular代码好像一直在跑!打开chrome性能调试工具,记录了5秒,密密麻麻的调用栈,惨不忍睹!问题1:真正的罪魁祸首是angulardirtycheck,发生了cycledirtycheck吗??在搞清楚这个问题之前,先介绍一下angulardirtycheck这个大佬。Rope1:angular2及以上版本的脏检查方式新一代angular改变了angularjs(ng1)中被鄙视的脏检查策略。angularjs::的数据流策略的变化是周而复始,直到稳定下来。也就是说,在异步事件触发脏检查后,在脏检查过程中某个scope的值发生变化后,会再次触发脏检查,直到scope上的数据稳定。在这样的过程中,很难找出是哪一次脏检查,是哪个对象被改变导致了dom的更新。Angular的策略:从组件树的顶部到底部,每个组件依次进行自己的脏检查。如下图所示,左边的模型是DOM树,右边是组件树。每次更改模型数据时,都会触发脏检查。每次检查都从根节点开始,沿一个方向向下进行。在此检查时间段内,模型不允许修改,模型数据处于稳定状态。谁告诉angular做脏检查改变方式angularjs:Injectngeventstonotifydirtychecks,比如js原生的setTimeout中不能改变model的值,必须注入ngevent$setTimeout。angularjs之道:zone.js(也是大佬,想了解的可以看我的一篇NgZone.js文章https://segmentfault.com/a/11...)。你什么都不用做,随便写native,自然会有人帮你通知angular做dirtycheck。Answer1:从以上线索可以看出,Angular中并没有发生循环脏检查。Question2:你是从组件树的最顶层开始,对组件一个一个进行脏检查吗?会不会是组件树太大,检查的日志太多,执行次数太多?单个组件脏检查?Rope2:先来看看angular的脏检查策略。上图中圈出的一段代码changeDetection:ChangeDetectionStrategy.OnPush。它有什么作用?它可以改变脏检查的策略。上面说过,组件树中某个节点的某个事件会触发脏检查,那么整个组件树的每个节点也会进行脏检查,对吧?是的,默认策略是这样的。但是Angular可以更聪明并使用OnPush策略。该策略将允许组件在输入的对象引用指针没有变化时(注意:是对象引用指针的变化)跳过节点及其子节点的脏检查。示例1:@Component({selector:'echart',template:`

`,changeDetection:ChangeDetectionStrategy.OnPush})exportclassChartComponent{@Input('option')选项:任何;constructor(){}ngOnInit():void{window.addEventListener('resize',this.resize,true);}click():void{this.option={title:'hi'}}resize(){this.option.title='Hi'}}本例中,当页面窗口发生变化,在resize过程中修改了title,dom将不会更新。如下图所示:当click方法被触发时,组件会进行脏检查并更新dom。如果使用OnPush策略,resize中的修改可以更新dom怎么办?代码如下constructor(privateref:ChangeDetectorRef){}resize(){this.option.title='Hi'this.ref.markForCheck();}还是从上到下,angular会找到所有包含这个组件的路径组件逐一进行脏检查(即使顶层组件设置了onPush策略)如下图:answer2:很明显,一个大的组件树不会造成太多的脏检查,因为我们添加了onPush策略,输入没有变化。以下组件不应进行脏检查。虽然添加了onPush策略,但是页面上还有很多不应该运行的代码还在执行。添加onPush策略的记录,右图是添加onPush策略的记录,可以看到添加的onPush策略还在执行script、render、Painting。我们再看一下调用栈,如下图所示:从图中我们发现执行了一段调用栈NgZone的代码。还记得Rope1中提到的NgZone吗?发起脏检查的通知器,它代理原生事件,任何原生异步事件的触发都会导致NgZone运行。那么循环中一定有原生事件在运行![注:细心的人可能还会发现,图中有些同学会发现正在执行angular.core的代码。answer2中不是已经说了不会有dirtycheck吗?确实没有进行脏检查。Rope2也解释了dirtycheckingstrategy的原理。不要忘记在脏检查之前,会检查组件的输入引用,以确定组件是否应该进行脏检查。]问题3:谁在骚扰NgZone?我们继续看性能分析中的调用栈。只要函数进入了“犯罪现场”,我们就可以找到它的足迹。看看这个!我们找到了animation.js执行的一个步骤函数。看看这个!果然有一个requestAnimationFrametimer()native事件一直在执行,一直没有被销毁!Answer3:原来流氓是echarts的animation.js或者echarts的核心组件zrender在动画结束后没有调用动画中的stop方法。总之,真凶是echarts!(如果你用的是echarts,可以打开调试工具,可以看到那段代码一直在循环执行)凶手找到了,受害者还需要安抚,怎么解决?放弃电子图表?你要知道,有一种流氓,让你恨之入骨,杀不死你。不得不承认echarts的绘图效率在移动端还是不错的。还有地图。谁会用其他图表插件给你画一张某个城市的地图。。。这时候我又得抱住Angular了。虽然我们管不了echarts,但是NgZone是一个很开放的家伙。给我们很大的自由操作空间,就像下面的示例一样,使用runOutsideAngular来跳过zone.js对包装函数内部执行的代码的包装。echarts的requestAnimationFrame再也不会来骚扰我们的NgZone了。导出类EChartsComponent实现OnInit,OnDestroy{@Input()chartid:string;@Input('option')选项:任意;私人图表:任何;@ViewChild('root')私有根;构造函数(私有ngZone:NgZone){}resizeListener=()=>this.resize();ngOnInit():void{this.ngZone.runOutsideAngular(()=>{this.chart=echarts.init(this.root.nativeElement);this.chart.setOption(this.option,true);window.addEventListener('resize',this.resizeListener,true);})}}优化后5s内的表现如图:故事的结局虽然优化结果并不完美,但是从图中可以看出脚本(echarts的烂代码)在页面稳定静态的时候还在执行。如何解决echarts的循环requestAnimationFrame问题,后续问题留给echarts团队解决。