likes
comments
collection
share

Angular的14种优化策略

作者站长头像
站长
· 阅读数 40

简介

在这篇文章中,我们将去讨论如何让你的angular应用变得更小更快的14种方式:

  1. 启用生产模式
  2. AOT
  3. Minification
  4. 避免视图中的函数调用和 getter
  5. 纯管道
  6. 懒加载module
  7. 代码拆分
  8. onpush变更检测
  9. 异步管道
  10. ngDoCheck
  11. 函数追踪
  12. Zone.js
  13. 取消订阅 observables
  14. web workers

构建和部署优化

生产优化

在部署您的应用程序之前,请确保您创建了一个生产版本。此模式执行许多在开发构建中不可用的重要优化,包括AOT编译、minification和tree shaking。

JIT 和 AOT

Angular提供了两种编译模型:JIT (just-in-time),它在运行时编译应用;AOT (ahead-of-time),它在构建时编译应用。默认情况下,开发编译使用AOT编译。 AOT 在构建时进行编译,仅生成编译后的模板,并从部署捆绑包中删除 Angular 编译器,从而将您的应用程序负载减少大约 1MB(大约是 Angular 编译器的大小)。

Minification

我们的 JavaScript 代码中的许多字符,包括空格、换行符、注释和块定界符,仅用于可读性和视觉目的。它们不是代码正确运行所必需的。Minification删除了这些字符,简化了名称,并忽略了无法访问的代码。通过缩小代码,您可以加快页面下载和执行时间。 现在,比较下缩小前和缩小后的代码: 缩小前:

var app = angular.module("myApp", []);
    app.controller("myCtrl", function($scope) {
    $scope.title = "Lorem";
    $scope.subtitle = "Ipsum";
    if (false) {
        console.log('Lorem Ipsum Dolor');
    }
});

缩小后:

var app=angular.module("myApp",[]);app.controller("myCtrl",function(l){l.title="Lorem",l.subtitle="Ipsum"});

构建优化

Build Optimizer由Angular团队创建,是一个进一步优化Angular Webpack构建的工具。它确定了可以在构建时删除而不会产生副作用的代码。例如,Build Optimizer可以从AOT构建中删除@Component这样的Angular装饰器。因为编译器会从这些装饰器中提取所有必要的信息,所以只在编译时需要它们。

使用angular优化创建生产构建

如果您执行生产构建,将应用上述优化。以下 CLI 指令允许通过创建生产构建进行直接部署

ng build --prod

之后,生成的输出目录可以复制到web服务器。如果您使用了prod标志,您可能会开始看到没有它就不会出现的错误。但这是一件好事:现在您有机会捕获并解决原本只会在稍后的运行时出现的错误。

启用生产模式

默认情况下,Angular运行在调试模式下,它会添加一些断言检查,但每次也会运行两次ChangeDetection,以确保绑定值没有意外的更改。为了只调用一次ChangeDetection,你需要在Angular应用中添加以下代码来启用生产模式:

import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';

if (environment.production) {
    enableProdMode();
}

代码优化

懒加载模块

Angular提供了惰性加载,这是一种按需加载页面的简单方法。使用惰性加载,只有当用户导航到特定模块的路由时,模块才会被加载。Angular团队在路由器中内置了这个特性,所有这些都是在底层为你完成的。因此,延迟加载使用起来很简单。

然而,有一个事实被忽略了,利用惰性加载的必要条件是:它需要更多的模块。我们将应用程序划分为模块的一个好处是能够按需加载模块。因此假如你写了3万行代码,它们都在一个模块中,你就不能利用惰性加载功能,你的应用程序可能会越来越慢。

没有懒加载的路由:

const routes: Routes = [
  {
    path: 'customers',
    loadChildren: CustomersComponent
  },
  {
    path: 'orders',
    loadChildren: OrdersComponent
  }
];

路由懒加载:

const routes: Routes = [
  {
    path: 'customers',
    loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule)
  },
  {
    path: 'orders',
    loadChildren: () => import('./orders/orders.module').then(m => m.OrdersModule)
  }
];

代码拆分

减少初始加载时间和加快页面导航速度的一种方法是代码分割。随着web应用程序变得越来越复杂,发送给用户的JavaScript文件也越来越大。大型JavaScript文件会延迟浏览器中的交互时间,特别是对于移动用户。

代码分割有效地减少了应用程序中的JavaScript包,而不损失任何特性。这种技术允许您将JavaScript代码分解为多个部分,当用户导航到不同的路径时,或者当用户打开或展开组件时,可以逐步加载这些部分。

主要有两种方法:

  • 组件级代码分解,即使没有路由导航,单个组件也可以惰性加载;
  • 路由级代码分解,单个路由被惰性加载。

OnPush变更检测

默认情况下,Angular会检查每个组件,看看是否有变化,并相应地更新视图。虽然这是一个相对较快的过程,但随着应用程序的增长,这些频繁的更新检查会变慢。

与默认策略不同,OnPush更改检测只对@input参数中的更改做出反应,或者当您手动触发检测时。

要启用OnPush更改检测,在组件装饰器中定义这个策略:

import { ChangeDetectionStrategy, Component } from '@angular/core';

@Component({
    selector: 'app-user-list',
    templateUrl: './user-list.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListComponent {
}

ngDoCheck生命周期钩子

每次运行变更检测时,Angular 都会调用 ngDoCheck。因此,ngDoCheck 是组件中添加计算密集型或缓慢的自定义逻辑或检测 Angular 会忽略的更改的完美位置。您可以将复杂的计算移动到 ngDoCheck 生命周期挂钩中,并在您的视图中引用计算值。请记住,缓存复杂的计算结果会产生更好的性能。但由于该生命周期调用频繁的缘故,我们不应在该生命周期内使用大量复杂且不能缓存的计算。

异步管道

在使用可观察对象时,调用subscribe方法,但忘记调用unsubscribe,随后会导致内存泄漏。内存泄漏是异步管道成为您的朋友的原因:它为您处理所有的清理工作。它不仅为您订阅,而且还在您关闭组件时负责取消订阅,并且在每次更新时调用markForCheck。因此,异步管道是OnPush更改检测策略的完美匹配。

async管道允许你直接在视图中使用RxJS的可观察对象。从下面的代码中可以看到,每当异步管道更新一个值时,它都会自动为您调用markForCheck。因此,您只注入数据服务,而不将实际值放在组件中的字段中。相反,您可以添加一个对可观察对象的引用。现在,当有更新发送到这个可观察对象时,你的组件也会检测到变化:

<span>Wait for it... {{ greeting | async }}</span>

避免视图中的函数调用和 getter

当绑定到一个对象时,Angular会非常快速地在模型属性中执行变更验证,因为它不需要执行任何函数。然而,当你绑定到一个函数或JavaScript getter时,Angular必须运行你的函数来检查值是否发生了变化。在某些情况下,Angular会频繁地执行变更检测,导致应用出现严重的性能问题。如果可以避免这种情况的话,永远不要在Angular模板表达式中绑定函数或getter。相反,使用纯管道可以让Angular在值不变的情况下高效地跳过管道执行。你也可以手动计算组件控制器中的值,并在需要时重新计算它们。

纯管道

有时,您需要在视图中调用函数,但在许多情况下,您可以使用管道代替。

import {Pipe, PipeTransform} from '@angular/core';

@Pipe({name: 'repeat'})
export class RepeatPipe implements PipeTransform {
  transform(value: any, times: number) {
    return value.repeat(times);
  }
}

Zone.js

有时候,你的视图没有更新,后来你会发现,当模型被异步函数(比如setInterval、setTimeout、鼠标事件或promise拒绝)更新时,Angular不会检测到变化。为了解决这个问题,人们求助于AngularJS替代这些异步函数,但他们仍然必须以编程化的方式更新视图。

Angular2引入了Zone.js,通过自动更改检测来给这些异步浏览器函数打补丁。使用Zone.js,您的异步函数能够自动更新视图。这是一个受欢迎的增强,但它也意味着每次异步执行都会触发变更检测。而触发的变更检测就是 ngZone 发挥作用的地方。在你确定哪些异步函数不影响模型并且不需要视图更新之后,你可以告诉 ngZone 在 Angular 上下文之外运行这些函数:

export class AppComponent {
  constructor (ngZone: NgZone) {
     ngZone.runOutsideAngular(() => {
      // runs outside Angular zone, for performance-critical code

      ngZone.run(() => {
      // runs inside Angular zone, for updating view afterwards
     });
   });
  }
}

此 Stackblitz 演示中,您可以看到在 Angular 区域之外运行循环如何不会导致 UI 在每个 setTimeout 周期后刷新。

Unsubscribing Observables

取消订阅很简单:存储订阅,然后使用ngOnDestroy生命周期钩子函数在订阅对象上调用取消订阅。

幸运的是,ngOnDestroy生命周期钩子创建了一个很好的模式,所以你可以在订阅期间适当地处理内存。对于每个组件或指令,使用ngOnDestroy回调方法,在其中调用订阅的unsubscribe:

import { OnDestroy } from '@angular/core';

export class MyCleanupComponent implements OnDestroy {
    private _subs: Subscription;

    constructor(router: Router) {
        this._subs = router.events.subscribe(event => {
            //Event must be handled here...
        });
    }

    ngOnDestroy(): void {
        this._subs.unsubscribe();
    }
}

函数追踪

操作DOM是一项代价高昂的任务。默认情况下,ngFor会执行一个简单的相等性检查,看看项目是否发生了变化。ngFor指令提供了trackBy函数,它决定了Angular如何跟踪集合中对象的变化,以便ngFor能够执行有效的更新。

当对象在集合内改变时,指令必须重绘正确的DOM元素。由于并非所有DOM节点都受到影响,因此只会重新呈现已更改的元素。

<li *ngFor="let item of strategyItem; trackBy: trackByFn">\{{ item }}</li>
public trackByFn(index, item) {
    if (!item) return null;
    return item.id;
}

web workers

虽然没有看到很多应用程序实现了 Web Worker,但它们可以成为在后台线程中运行 CPU 密集型任务的有用资产,而不会阻塞应用程序的主线程或冻结 UI。

但是,在你的Angular应用中实现web worker之前,要考虑两个限制:

  • 一些环境或平台(如@angular/platform-server)不支持web worker。
  • Angular CLI不支持通过@angular/platform-webworker在webworker中运行Angular本身。

寻找优化的操作技巧

现代浏览器提供性能分析工具来帮助识别运行缓慢的代码。你可以使用一个叫做Webpack Bundle Analyzer的模块进一步扩展你的工具带,它允许你可视化Webpack生成的文件。每个文件都由一个矩形表示,其大小与文件大小相关。

Webpack Bundle Analyzer将帮助您识别包中包含的模块,占用更多空间的模块,以及错误添加的模块。Bundle Analyzer还可以进入缩小的Bundle,以发现它们的实际大小。

Angular的14种优化策略

Lighthouse 用作内置的 Chrome 开发工具,用于识别和修复影响网站性能、可访问性和用户体验的常见问题。

Angular的14种优化策略