资讯专栏INFORMATION COLUMN

寻找真凶Echarts or Angular

sumory / 3476人阅读

摘要:我们再来看一下调用栈,如下图从图中我们发现了一个调用栈的代码执行过,还记得里提到吗发起脏检查的通知者,它代理了原生事件,任何一个原生异步事件的触发都会导致的运行。

寻找真凶Echarts or Angular

这是一篇故事,就如同技术,我们所追求的不是一个结局,而是那些深受启发与共鸣的过程,那是我们成长的经验与生产力的积淀!

故事开始于“疯了”的ionic3应用

页面打开,什么也没做5s里angular的代码似乎一直在跑!

打开chrome性能调试工具,recorded 5秒,密密麻麻的调用栈,惨不忍睹!

Qustion1:难道真凶是angular脏检查,发生了循环脏检查??要弄清这个问题前,我们先来介绍angular脏检查这个大人物。

Rope1:angular2及以上版本脏检查方式

新一代的angular一改angularjs(ng1)中受人唾弃的脏检查策略。

数据流的改变

angularjs的策略::是again and again直到稳定。也就是说在异步事件触发脏检查后,脏检查发生过程中某一个scope值改变后,会再次触发一次脏检查直到scope上数据稳定不变。这样一个过程很难找到一次脏检查是哪一次、哪一个对象发生改变导致的dom更新。
angular的策略::从组件树顶至下,各组件依次做自己的脏检查。如下图,左边是model右边是dom树也是组件树,每一次model数据的改变,触发一次脏检查,每次检查从跟节点开始单向向下,在这次检查时间片段中不会允许对model做修改,model数据处于稳定状态。

谁告诉angular做脏检查的改变

angularjs的方式: 注入ng事件来通知脏检查,例如,你不能在js原生的setTimeout里改变model值,必须注入ng事件$setTimeout。
angularjs的方式: zone.js (它也是个big man,想了解它可以看我的一篇NgZone.js文章https://segmentfault.com/a/11...)。什么都不用做,原生随意写,自然有家伙帮你通知angular去做脏检查。
answer1:从以上线索可以断定,不是angular发生了循环脏检查

Qustion2:是不是从组件树顶向下逐一组件进行脏检查,会不会是组件树太庞大log了太多checked,执行了太多次单个组件脏检查?

Rope2: angular脏检查策略

先来看看现场,上面的图中圈出了一段代码changeDetection: ChangeDetectionStrategy.OnPush,它是做什么的呢?它可以改变脏检查的策略。上面提到一颗组件树中某一个节点某个event触发了脏检查,整棵组件树每一个节点都会跟着做脏检查对吧?对的,默认的策略是这样的。但angular可以更聪明点,使用OnPush策略。这个策略会让该个组件在input对象引用指针没发生变化时跳过该节点及该节点子节点脏检查(注意:是对象引用指针的变化)。
example 1:

@Component({
  selector: "echart",
  template: `
`, changeDetection: ChangeDetectionStrategy.OnPush }) export class ChartComponent { @Input("option") option: any; 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(private ref: ChangeDetectorRef) {}
  resize() {
    this.option.title = "Hi"
    this.ref.markForCheck();
  }

依然是从上到下,angular会找到包含该组件的路径的所有component进行逐一脏检查(即使顶层组件设置了onPush策略)如下图:

answer2:很显然组件树庞大不会引起脏检查多,因为我们已经加了onPush策略,input也未改变,该组件及该组件向下的组件都不应该发生脏检查
虽然加了onPush策略但页面上依然有很多不该运行的代码一直在执行,下图为页面稳定静止状态下记录5s内的浏览器执行情况,左图为未加onPush策略的记录,右图为已加onPush策略的记录,可以看见已加onPush策略的依然有script,render,Painting在执行。


我们再来看一下调用栈,如下图:

从图中我们发现了一个调用栈NgZone的代码执行过,还记得Rope1里提到NgZone吗?发起脏检查的通知者,它代理了原生事件,任何一个原生异步事件的触发都会导致NgZone的运行。那么一定是有原生事件在一直Loop执行!

【注:细心的人可能还发现图里有一些同学会发现有angular.core的代码在执行,不是在answer2中已经说了不会脏检查了吗?确实不会在做脏检查,rope2中也说明过脏检查策略的原理,别忘了再脏检查前还会check组件input引用来决定是否该组件做脏检查呢】

Qustion3:谁在调戏NgZone?
我们再继续看下性能分析里的调用栈,只要该函数进入过"犯罪现场"我们都能找到它的足迹。Look this!我们找到了一个animation.js执行的step函数。

look this!果然有一个requestAnimationFrame定时器()原生事件一直在执行,且从未销毁!

answer3:原来流氓是echarts的animation.js或者说是echarts核心组件zrender在动画结束后没调用animation中的stop方法,总之真凶是echarts!(如果你正在使用echarts,可以打开调试工具,可以看到那段代码一直在loop执行)

凶手找到了,受害者还需要安抚解决,如何解决?弃用echarts?你要知道有一种流氓叫让你讨厌又让你干不掉,不得不承认echarts的绘制效率在移动端还是不错的,还有地图,用其它chart plugin谁来给你画某某市地图...
此时不得不再捧一把Angular,虽然我们管不了echarts,但NgZone是一个很开放的家伙。给我们很多自由操作的空间,就像下面的sample,使用runOutsideAngular将包裹的函数内部执行的代码都跳过zone.js的包装。那个echarts的requestAnimationFrame再也不会骚扰咱们的NgZone了。

export class EChartsComponent implements OnInit, OnDestroy {
  @Input() chartid: string;
  @Input("option") option: any;
  private chart: any;

  @ViewChild("root")
  private root;
  constructor(private 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内perfermance如图:

故事的结局

虽然优化的结果不是最完美的,从图上可以看到页面稳定静止状态下还是有script(echarts的bad code)在执行。如何去解决echarts的loop requestAnimationFrame问题,后续提issue留给echarts团队去解决吧。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/89909.html

相关文章

  • echarts使用总结

    摘要:最近一段时间做了一个使用的图表项目。由于理解能力有限,使用起来并非畅通无阻。所谓好记性不如烂笔头,现将一些比较关键的点记录一下,供后续查看。 最近一段时间做了一个使用echarts的图表项目。由于理解API能力有限,使用起来并非畅通无阻。所谓好记性不如烂笔头,现将一些比较关键的点记录一下,供后续查看。 一 使用方法 项目:ionic+angular4+echarts 1.由于打包原因,...

    spademan 评论0 收藏0
  • 如何在AngularJS中使用插件

    摘要:开发我认为在中使用其他插件的最好方法是使用指令的形式在项目中引入,这样做有许多好处,其中最明显的好处便是当项目中需要引入多种插件时可以使用各种不同的指令将他们分离并且还具有一次开发各处使用的组件化特点。 在实习期间,由于项目的需求,我第一次系统的使用了angular这一优秀的js框架,其所拥有的许多优秀特性极大的方便了项目的开发,然而在开发中也遇到过不少的问题,现在趁自己被抓回学校无所...

    baoxl 评论0 收藏0
  • angular4中引入echarts

    摘要:安装在项目中引入文件使用在你真正需要使用指令的中堆叠区域图邮件营销联盟广告视频广告直接访问搜索引擎周一周二周三周四周五周六周日邮件营销总量联盟广告总量视频广告总量直接访问总量搜索引擎总量直达营销广告搜索引擎邮件营销联 1.安装ngx-echarts //if you use npmnpm install echarts --save //if your angular version ...

    ormsf 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<