更快的Angular应用程序

应用程序的运行时性能完全掌握在开发者自己手中。接下来,我们以一个简单的应用来演示如何进行性能优化。

一个简单的应用

我们将会在这个应用程序中,一步一步的引入所有可行的性能优化方案。

在图片中我们可以看到应用的概览,包含以下功能:

  1. 两个部门的职员列表(销售部和研发部)

  2. 每个职员对应一个名字和一个数字。数字是通过计算生成的,不需要关注太多细节,记住是通过计算生成的就好。

  3. 数字右边有一个删除按钮,可以删除对应的职员。

  4. 每个部门列表有一个输入框,我们可以输入员工姓名,将其添加到列表中,同时会计算出一个数字与该员工对应。

就这么多,应用的组件结构如下:

AppComponent 包含两个用来显示部门职员列表的 EmployeeListComponent

看一下 EmployeeListComponent 的模板代码:

title="Department">{{ department }}

placeholder="Enter name here" matInput type="text" [(ngModel)]="label" (keydown)="handleKey($event)">

*ngFor="let item of data">

matLine title="Name">

{{ item.label }}

title="Score" class="mat-chip mat-primary mat-chip-selected" color="primary" selected="true">

{{ calculate(item.num) }}

title="Delete" class="fa fa-trash-o" aria-hidden="true" (click)="remove.emit(item)">

首先,我们显示出部门名称。然后,我们创建了一个文本域用来输入职员姓名。最后,我们使用一个列表来显示所有该部门下的职员。 calculate 方法计算出员工对应的数字。代码如下:

const fibonacci = (num: number): number => {

if (num === 1 || num === 2) {

return 1;

}

return fibonacci(num - 1) + fibonacci(num - 2);

};

@Component(...)

export class EmployeeListComponent {

@Input() data: EmployeeData[];

@Input() department: string;

@Output() remove = new EventEmitter<EmployeeData>();

@Output() add = new EventEmitter();

label: string;

handleKey(event: any) {

if (event.keyCode === 13) {

this.add.emit(this.label);

this.label = '';

}

}

calculate(num: number) {

return fibonacci(num);

}

}

EmployeeListComponent 的定义很简单:

  • 两个Input:

  • data 部门职员列表

  • department 部门名称

  • 两个Output:

  • remove 删除职员时触发

  • add 新增职员时触发

Input 属性将由 AppComponent 传递给 EmployeeListCompoment

业务的计算

我们使用 calculate 方法来模拟业务的计算逻辑,它只是调用了 fibonacci 函数而已。真实的业务逻辑过于复杂,而且对我们的演示来说没有必要。

应用结构回顾

  1. 一个根元素 AppComponent.

  2. 两个 EmployeeListComponent.

  3. EmployeeListComponent 会通过计算分配给职员一个数字。

输入速度

你可以从下面的图片中看到输入体验,这是当两个列表共有140条记录时的状态。注意,我们的打字速度是极快的,而界面出现了明显的卡顿。

很明显有问题,我们打开 Chrome DevTools > Performance 进行 debug。

从截图中可以看到,占用时间最长的是脚本运算。如果我们转到 Bottom-Up 标签页,可以看到如下情况:

可以看到函数 fibonacci 占用了几乎所有的脚本运算时间。通过在 EmployeeListComponent 的 calculate 方法中添加 log 信息,我们可以看一下它的执行频率:

从图中可以看到,当我们输入一个字符时, fibonacci 函数至少对列表中的每一项调用了两次 ( 在非 production 模式下为四次)。而且,在输入框获取焦点时,和失去焦点时,也会调用相同的次数。即使列表中的每一项其实已经经过计算并且显示在界面上,应用还是为他们重新进行计算。之所以出现这种情况,是因为 Angular 的变更检测在上述事件发生时会被触发。当变更检测被触发后,所有模板中的表达式将被重新计算,并和原先的值进行比较,如果发生改变,则更新 DOM。这意味着,每一次变更检测,都会导致模板中的表达式被重新计算。所以,要避免在模板中插入运算量大的表达式。

现在,我们很清楚列表中的数据并没有发生该变(仅仅是输入了职员姓名,还未添加到列表之前),Angular 不需要在这时更新 DOM。问题是,如何告诉 Angular 此时不需要进行变更检测?

On Push 变更检测机制

答案是使用另一种变更检测机制。实际上, OnPush 变更检测机制正是我们寻找的方式。通过设置 changeDetection:ChangeDetectionStrategy.OnPush,可以让 Angular 明白只有在组件的 Input 属性发生变化时,才运行变更检测。注意 Angular 使用 === 来比较 Input 属性值是否发生了改变

Components 与 Functions

为了解释 OnPush 的运行方式,我们将 EmployeeListComponent 以函数的方式进行模拟。假设函数的参数就是组件的 Input 属性,函数的返回值就是界面渲染的 DOM 结构。

function runChangeDetection() {

console.log('Detecting for changes');

}

function EmployeeListComponent(args) {

const shouldRun = Object.keys(args).reduce((a, i) => {

return a || args[i] !== EmployeeListComponent[i];

}, false);

Object.keys(args).forEach(i => {

EmployeeListComponent[i] = args[i];

});

if (shouldRun) {

runChangeDetection();

}

}

const f = EmployeeListComponent;

const data = [e1];

上面的代码中,我们把 EmployeeListComponent 作为一个函数,有三个要素:

const shouldRun = Object.keys(args).reduce((a, i) => {

return a || args[i] !== EmployeeListComponent[i];

}, false);

当 args 对象的每一项属性值都和函数对应的属性值相等时,返回false,只要有一项不相等,则返回 true。

在这之后将函数的属性值更新:

Object.keys(args).forEach(i => {

EmployeeListComponent[i] = args[i];

});

最后,如果值发生了改变,就运行变更检测。

if (shouldRun) {

runChangeDetection();

}

假如传给 EmployeeListComponent 的参数中包含一个属性名 data.

let arr=[];

EmployeeListComponent({data: arr});

那么,当运行以下代码时:

arr.push(1);

变更检测不会发生,因为 data 属性的值还是指向同一个数组 arr. 使用 === 比较,是相等的。

可以通过下面的方式,来改变 arr 的引用。

arr.push(1);

arr = arr.slice()

这时, Angular 才会认为 Input 属性的值发生了变化,运行变更检测。

经过梳理后,我们将应用的组件更新如下:

@Component({

changeDetection: ChangeDetectionStrategy.OnPush,

...

})

export class EmployeeListComponent {

@Input() data: EmployeeData[];

@Input() department: string;

handleKey(event: any) {

// 当按下回车时,触发 add 事件

if (event.keyCode === 13) {

this.add.emit(this.label);

this.label = '';

}

}

...

}

AppComponent

@Component({

template: `

`

})

export class AppComponent implements OnInit {

salesList: EmployeeData[];

addToSales(name: string) {

this.salesList = this.salesList

.unshift({ label: name, num: this.generator.generateNumber(NumRange) })

// 注意这里

.slice();

}

...

}

这样,每当用户按Enter键时,我们将使用add事件发出标签的值。AppComponent将使用其addToSales方法处理输出,该方法将新职员推送到salesList,复制整个数组,并将返回的新引用设置为salesList的值。 然而,我们引入了两个问题,导致它将是缓慢的:

  • 每次添加员工时,我们都需要复制整个数组。垃圾收集器还应该运行以清理未使用的内存(原来的arr)。

  • 它需要很多内存。我们为新数组分配的内存可能很大,这取决于数组的大小。为了处理这两个问题,我们可以使用高效实现的不可变数据结构,如immutable .js。

引入 Immutable.js

immutable.js提供了一组不可变的数据结构。它们都有两个基本属性:

  1. 它们是不可变的(显然),因此当我们打算应用一个操作来改变这些数据结构中的任何一个实例时,我们将得到一个新实例(分别具有一个新引用)。

  2. 基于immutable.js 的修改操作会生成新的数据结构,不会复制整个数据结构,它将尽可能多地重用原数据结构。

这是第一点的一个例子:

import { List } from 'immutable';

const data = List([a, b, c]);

console.log(data.toJS()); // [a, b, c]

const appended = data.push(d);

console.log(data.toJS()); // [a, b, c]

console.log(appended.toJS()); // [a, b, c, d]

从上面的代码片段可以看出,当我们调用List实例的push方法时,我们会得到一个新的List。最重要的是,原来的数据保持不变。 基于第二个属性,我们可以得出这样的结论:来自immutable.js的不可变数据结构将比我们在上面的示例中复制整个数据结构更有效率。

OnPush And Immutable.js

下面优化前后的性能对比图:

看起来我们得到了一个很好的优化。我们快了三倍!根据基准测试,在销售部输入字符串AngularConnect,对于非优化版本,输入13124.81ms,对于优化版本,输入5804.12ms。 现在我们看一下应用的体验:

。。。依然很慢。正如我们所看到的,calculate方法执行的频率下降了,但调用的次数仍然很多。如果我们仔细观察,可以注意到,这一次我们只重新计算销售部所有员工的数值。OnPush优化几乎成功了。

在输入时(按 Enter 之前),我们不会得到新的salesList实例,因为我们没有进行任何改变它的值的操作。为什么当前组件依然触发了变更检测?我们不是使用了 ChangeStrategyDetection.OnPush 吗?

答案在于OnPush的工作方式。使用OnPush变更检测策略,当我们将一个新值传递给组件的 Input 属性或当组件内部发生事件时,将触发该组件的变更检测。加粗部分在文档中不是很明显(因为原文档并没有加粗显示),但是在Angular core 仓库中的e2e测试中可以看到。

组件分割

为了解决这个问题,我们需要进行一些重构。这不仅可以帮助我们消除不必要的变更检测调用,还可以在应用程序中实现更好的逻辑解耦。

为此,我们将EmployeeListComponent分解为:

  • NameInputComponent——负责保存要添加到列表中的职员的名称。

  • ListComponent——将列出各个职员并计算他们的数值

现在看一下优化后的性能对比:

看起来图表上有个小故障。不,没有,我们只是快了几百倍——13124.81ms vs. 10.45ms。输入字符串为 “AngularConnect”。

这是用户在优化版本输入文本时的体验:

在“更快的Angular应用程序”系列的这一部分中,我们讨论了如何通过使用不可变的数据结构和自定义的变更检测机制来优化Angular应用程序的运行时性能。

我们首先介绍了一个示例业务应用程序,它列出了职员的两个部门,并使用大量计算为每个员工计算一个数值。

由于我们使用双向数据绑定,应用程序的相应出现了一些明显的迟滞——输入新职工的速度很慢。为了减少计算量,我们使用了OnPush变化检测策略和不可变数据结构。

尽管OnPush带来了一个显著的改进,但我们忽略了一个非常重要的细节——当给定组件中的一个事件被触发时,它会导致Angular运行变更检测。我们通过分解EmployeeListComponent解决了这个问题。

本文摘译自: https://blog.mgechev.com/2017/11/11/faster-angular-applications-onpush-change-detection-immutable-part-1/

github仓库地址: https://github.com/mgechev/optimizing-an-angular-application.git

ng-conf 专题视频地址:https://www.youtube.com/watch?v=ybNj-id0kjY

angular input_更快的Angular应用程序相关推荐

  1. 我如何构建Kubernetes集群,以便我的同事可以更快地部署应用程序

    by cheungpat 通过cheungpat 我如何构建Kubernetes集群,以便我的同事可以更快地部署应用程序 (How I built a Kubernetes cluster so my ...

  2. angular input_快速地上手Angular组件开发

    如果我会一些Javascript的基础知识,我可以快速地上手Angular吗?或者说,我是一名前端工作者,没有接触过Angular,我该如何快速地使用Angular进行日常开发呢?我是轻流前端团队的一 ...

  3. 使用缓存构建更快的 Web 应用程序

    使用 Java™ 技术的 Web 开发人员可以使用缓存实用程序快速提升他们的应用程序的性能.Java 缓存系统(Java Caching System,JCS)是一个用于 Java 应用程序的强大分布 ...

  4. ic卡写卡 angus_使用Angus更快地构建Web应用程序

    ic卡写卡 angus Nick's outstanding utility, Angus, has changed! Click here to read an updated post! 尼克杰出 ...

  5. 如何为我们的应用程序提供一个更小、更快的视频通话库

    正文字数:2848  阅读时长:4分钟 在研究如何使视频通话在未来更高效,更易于扩展时,Facebook意识到,最好的方法是从头开始重新设计库并重写整个库,也就是Rsys. 作者 / Ishan Kh ...

  6. python程序在函数内执行得更快

    http://www.cnblogs.com/nepaul/archive/2012/07/15/2592179.html 为什么Python程序在函数内执行得更快?(来源StackOverflow) ...

  7. python程序写完后点哪个运行快_让你的Python运行更快

    讨厌Python的人总是说,他们不想使用它的原因之一是它很 慢.嗯,特定程序(无论使用何种编程语言)是快还是慢,在很大程度上取决于编写该程序的开发人员以及编写优化而 快速的 程序的技能和能力 . 因此 ...

  8. [Mac入门] 如果更快的打开程序或文件

    今天有位网友问我在Windows下可以用Ctrl+R快速打开程序/文件夹等,在Mac下有没有相类似的功能?本文将介绍一些我的一般做法. 1.Windows下的Ctrl+R是什么? 我打算从解释Ctrl ...

  9. 【转】新思想、新技术、新架构——更好更快的开发现代ASP.NET应用程序(续1)

    上周星期天开通了博客并发布了第一篇文章<新思想.新技术.新架构--更好更快的开发现代ASP.NET应用程序>,汇集了一些比较流行的技术和开源项目,也把自己的程序架构.部分代码风格.前端表现 ...

最新文章

  1. 用XCA(X Certificate and key management)可视化程序管理SSL 证书(3)--创建自己定义的凭证管理中心(Certificate Authority)...
  2. AMBA、AHB、APB、AXI总线介绍和对比
  3. NameServer的启动
  4. 原来以为(Trie)字典树很难,后来发现不久这么一回事嘛
  5. 拼接字符串的优雅方式
  6. k3 xp服务器系统,如何在英文XP环境下安装k3系统.doc
  7. 虔诚的墓主人(bzoj 1227)
  8. python多线程守护线程_Python守护程序线程
  9. .net Cache 需要注意的地方
  10. vue for循环中使用setTimeout没用 笔记
  11. 思科CCIE证书电子档下载教程
  12. vijos 1066 树状数组
  13. 苏轼一生最智慧的20首诗词,不读懂不足以谈人生
  14. 机器学习基本概念-有监督学习和无监督学习
  15. python鸭制作类代码_python之类的多态(鸭子类型 )、封装和内置函数property
  16. 百度推广关键字质量度优化指南
  17. linux下启动,关闭oracle数据库
  18. 3U VPX XC7VX690T计算处理板
  19. C语言中函数参数传递的方式:值传递,地址传递
  20. Opencv Python图像处理进阶教程②

热门文章

  1. ajax只请求一次,关于ajax的请求只处理一次的问题
  2. 微信 input 照相机 呼出
  3. 编写shell管理脚本(二)
  4. JSP数据库操作和调用自己写的java类
  5. 实现iOS长时间后台的两种方法:Audiosession和VOIP(转)
  6. 预写式日志 - postgresql之WAL(Write Ahead Log)
  7. [转载] [转载] python 中NumPy和Pandas工具包中的函数使用笔记(方便自己查找)
  8. [转载] python中numpy.concatenate()函数的使用
  9. [转载] 为什么this()和super()必须是构造函数中的第一条语句?
  10. jmeter正则中常见的转义字符-笔记三