使用 DevTools 对 Angular 前端应用性能分析优化

信使

共 2588字,需浏览 6分钟

 ·

2022-01-10 12:31

使用 lighthouse 评分

以南宁IT派[www.nnitpai.com]为例记录分析优化过程,使用 Devtools lighthouse 对首页进行综合的评分:

7fa079c11369b8646ed7eb61c488302b.webp

性能评分勉强及格差强人意,切换到 performance 性能选项卡:

6ad096b843a6134d43e47cc9a6d05050.webp

记录的同时,可以依次滚动页面到底部,暂停看看分析结果:

20d23eac4b1fe57779c829bb98958fcb.webp

发现一推很深的函数调用,放大具体看看:(记得要用本地开发环境来查看,这样可以方便看未编译版本中具体的组件或者函数)

5d6d88d0b4ff5e10cf59b6d4b7a4739f.webp

发现大部分的深度调用都与这个 MenuComponent 组件有关,不断的在调用刷新,可以看到一个 executeTemplate 这个函数,这个是angular对模板里面的变量或者函数执行计算值,已检测是否有变化,相应进行渲染。

看看 MenuComponent 组件模板关键部分:

<ng-container *ngIf="content">
  <header *ngIf="screen.eq('gt-sm')" class="header" #header>
    //
  header>
  <div class="drawer">
    <mat-toolbar *ngIf="screen.eq('lt-md')">
      //
    mat-toolbar>
    <div [hidden]="!(screen.eq('lt-md') && isDrawer)">
      //
    div>
  div>
ng-container>

里面有个方法调用screen.eq()在eq方法打个log看看日志:

4b8a7b3f09bd29a23a01e3550dd3e6dc.webp

只要页面有事件发生,比如dom的更改,这个log一直飙升,导致严重的性能问题,问题应该就是在这里,为什么会被反复执行?

简单了解 Angular 的默认更新检测机制

有几种情况会触发 Angular 检测:

  • 用户的输出操作,点击、提交、滚动等等
  • http 请求
  • 定时事件,setTimeout\setInterval

变更检测机制是 Angular 内置的框架功能,可确保组件的数据与HTML模板视图进行自动同步,对于模板中使用的表达式,它将当前值与先前值进行比较,如果值不同则 isChanged,然后进行更新,组件的检测机制:

  • Default: 默认的检测机制,比较事件发生前后的模板表达式值来决定是否更新视图,只要有更新,会更新整个数组树。
  • OnPush: 检测组件的Input输入或者异步管道订阅的数据发生变化时才会更新;

寻找问题的原因

当页面滚到在背景视频的时候,log的输出特别的密集,仔细观察这个组件有DOM数据的刷新:

67e806e01acdbb07e2c94fe14ccbb6ab.webp

这应该是使用了定时器更新了当前的播放时间,导致 angular 不断触发监测机制。

解决问题

把组件默认监测机制更改为OnPush

从而可以忽略外部其他组件的变更周期,当输入属性发生变化时才会触发。

import {
  Component,
  OnInit,
  Input,
  ChangeDetectionStrategy,
from '@angular/core';
@Component({
  selector: 'app-menu',
  templateUrl: './menu.component.html',
  styleUrls: ['./menu.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})

手动更新检查

screen.eq('xs') 方法是用来检测当前屏幕的媒体查询,根据屏幕尺寸做出显示逻辑,使用 OnPush方式时,模板表达式不会再随着外部的检测周期影响到,当页面宽度发生变化时,我们就需要手动的去告诉组件更新检测。

新建一个媒体查询的服务,在组件里面订阅这个可观察对象,这个观察对象会在浏览器宽度发生变化时推送当前窗口的媒体查询值:

mqAlias$(): Observable<string[]> {
    return this.mediaObserver.asObservable().pipe(
      distinctUntilChanged(
        (x: MediaChange[], y: MediaChange[]) =>
          this.getAlias(x) === this.getAlias(y)
      ),
      map((change: any) => {
        return change.map((item: any) => {
          return item.mqAlias;
        });
      })
    );
  }

并在组件中引用

@Component({
  selector: 'app-menu-item',
  templateUrl: './menu-item.component.html',
  styleUrls: ['./menu-item.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MenuItemComponent implements OnInit {
  @Input() content: any;
  @Input() mobileMenu: any;
  showXs: boolean;
  constructor(
    private screen: ScreenState,
    private screenService: ScreenService,
    private cd: ChangeDetectorRef
  
) {}

  ngOnInit(): void {
    if (this.screenService.isPlatformBrowser()) {
      this.screen.mqAlias$().subscribe((alia) => {
        this.showXs = alia.includes('xs');
        this.cd.detectChanges();
      });
    }
  }
}

9006341d4f67ef35ebf9cf3cfedc4ac0.webp

总结

评分提升还是很明显的,对于后面组件的开发提供了很好的最佳实践,多理解熟悉 Angular 的内部运行机制,为项目开发带来更好的效益。

  • 要善于使用 lighthouse 进行检测评分,针对不同问题具体分析;
  • 多使用 Devtools 分析,查找问题的入口;
  • 不要在模板中使用函数或者getter,当有大量变更时,会存在很多的性能隐患;

接下来,会继续针对这个案例继续分析,使项目的 lighthouse 评分更加友好,提供给各位前端开发一些借鉴和交流。


浏览 43
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报