加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

寻找真凶Echarts or Angular

发布时间:2020-12-17 08:37:25 所属栏目:安全 来源:网络整理
导读:寻找真凶Echarts or Angular 这是一篇故事,就如同技术,我们所追求的不是一个结局,而是那些深受启发与共鸣的过程,那是我们成长的经验与生产力的积淀! 故事开始于“疯了”的ionic3应用 页面打开,什么也没做5s里angular的代码似乎一直在跑! 打开chrome性

寻找真凶Echarts or Angular

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

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

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

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

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

Rape1: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,想了解它可以看这https://segmentfault.com/a/11...)。什么都不用做,原生随意写,自然有家伙帮你通知angular去做脏检查。
answer1:从以上线索可以断定,不是angular发生了循环脏检查

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

Rape2: angular脏检查策略

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

@Component({
  selector: 'echart',template: `<div class="charts" #root></div>`,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的代码执行过,还记得Rape1里提到NgZone吗?发起脏检查的通知者,它代理了原生事件,任何一个原生异步事件的触发都会导致NgZone的运行。那么一定是有原生事件在一直Loop执行!

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

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

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

answer3:原来流氓是echarts的animation.js或者说是echarts核心组件zrender在动画结束后没调用animation中的stop方法,总之真凶是echarts!

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

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如图:

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读