Angular进阶之七: 不要在html template中使用函数(翻译)

本文解释了在Angular模板中使用函数导致性能问题的原因,介绍了纯管道和手动计算值的方法来减少函数调用,以及OnPush策略的应用。

为什么绝不应在 Angular Template中使用函数?

这篇文章已经很好的解释了这个问题,我们就不重复造轮子了,下面直接翻译一下这篇文章。https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496

Angular Template 功能是非常强大的。 我们可以通过 Directive 和 属性绑定,创造出非常复杂且结构清晰的视图:

<ng-container *ngIf="isLoggedIn">
<h1>Welcome {{ fullName }}!</h1>
</ng-container>

正因为Angular  Template 如此强大,所以当我们的视图变得更加复杂时,Angular Template 也很容易的变复杂。最终在Angular Template 中使用了函数:

<ng-container *ngIf="isLoggedIn">
<h1>Welcome {{ fullName }}!</h1>
<a href="files" *ngIf="hasAccessTo('files')">Files</a>
<a href="photos" *ngIf="hasAccessTo('photos')">Photos</a>
</ng-container>

虽然在Angular Template 上调用函数非常方便,但是它们会导致严重的性能问题。

接下来将解释性能问题的原因以及如何解决。

问题

假设我们有一个PersonComponent , 使用fullName 函数来显示传入人员的全名:

@Component({
  selector: ‘app-person’,
  template: `
    <p>Welcome {{ fullName() }}!</p>
    <button (click)="onClick()">Trigger change detection</button>
`
})
export class PersonComponent {  
    @Input() person: { firstName: string, lastName: string };     
    constructor() { }  
    fullName() {
        return this.person.firstName + ' ' + this.person.lastName
    }  
    onClick() {
        console.log('Button was clicked');
    }
}

此时,fullName 函数每次都会在Angular 变化检测时被调用,而且是很多次。而且每次在点击 Person 组件的按钮时fullName 函数都会被执行。

虽然只有点击按钮才会触发fullName 函数执行看起来并没有损耗多少性能,但当需求改变的时候比如:

@Component({
    selector: ‘app-person’,
template: `
    <p> Welcome {{ fullName() }}!</p>
        <div (mousemove)=”onMouseMove()”> Trigger change detection in mouse move </div>
        <button (click)=”onClick()”> Trigger change detection </button>
`
})
export class PersonComponent {
    @Input() person: { firstName: string, lastName: string };
     constructor() { }
     fullName() {
	return this.person.firstName + ’ ’ + this.person.lastName
     }
     onMouseMove() {
console.log(‘mouse move‘);
     }
     onClick() {
	console.log(‘button was clicked’);
     }
}

当鼠标移动到 Trigger change detection in mouse move 上,fullName 函数会被执行数百次。而且由于 fullName 函数是几个月前编写的,我们可能意识不到对新代码的影响。

此外,当父组件发生变更检测时也会使得PersonComponent 的 Template 执行fullName函数

<app-person [person]=’person’></app-person>
<button (click)=”onClick()”> Trigger change detection outside of PersonComponent </button>

每次点击父组件的按钮时都会在PersonComponent 内部执行fullName函数。

为什么Angular Template 中函数会被调用多次?

首先要说一下Angular 的变更检测(Change Detection)

Angular 变更检测的目标是在发生变化时,找出用户界面的哪些部分需要重新渲染。

为了确定<p>Welcome {{ fullName() }}! </p> 是否需要重新渲染,Angular 需要执行fullName() 来检查其返回值是否发生了变化。所以,变更检测运行了300次则fullName 函数就会被调用300次,即使返回值是一样的。这数百次运行函数就可能导致严重的性能问题。

如果我们使用getter 时,性能问题就变得很隐蔽:

@Component({
	template: `
 <p> Welcome {{ fullName}} !</p>
`
})
export class PersonComponent {
    
@Input() person: { firstName: string, lastName: stirng };
constructor() { }
get fullName() {
	return this.person.firstName + ‘ ’ + this.person.lastName;
}
}

此时, Angular Template 里没有任何函数的踪迹,但是 get fullName() 每次在变更检测时都会被调用。

如果使用变更检测的OnPush 策略会怎样?

比如我们的PersonComponent使用OnPush 策略

@Component({
	template: `<p> Welcome {{ fullName}} !</p>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonComponent {
    
@Input() person: { firstName: string, lastName: stirng };
constructor() { }
get fullName() {
	return this.person.firstName + ‘ ’ + this.person.lastName;
}
}

在PersonComponent 的父组件:

<app-person [person]=”person”></app-person>
<button (click)=”onClick”> Trigger change detection outside of PersonCompoent </button>

当点击父组件的button时,PersonComponent 的 fullName() 方法不会再执行。

因为在OnPush 策略下,变更检测会在第一次检查后被禁用,当输入属性(@Input)没有发生改变时,Angualr会跳过OnPush 控件以及其子控件。只有当输入数据(@Input)或者DOM事件被触发组件才进行变更检测。

但是,这并没有解决潜在的性能问题。因为每次Person Component 内部发生变更检测时, fullName 仍然会被执行:

@Component({
	template: `<p> Welcome {{ fullName}} !</p>
 <div (mousemove)=”onMouseMove()”> trigger in Person Component</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonComponent {
    
@Input() person: { firstName: string, lastName: stirng };
constructor() { }
get fullName() {
	return this.person.firstName + ‘ ’ + this.person.lastName;
}
onMouseMove() {
	console.log(‘mouse move’);
 }
}

如上,每次鼠标移动到trigger in Person Component 上时,仍会执行很多次的fullName

那么如何避免不必要的函数调用呢?

1 Angular Pure Pipe

Angular  Pipe 分为pure  pipe 和 impure pipe,对于pure pipe 而言只有在参数改变时被触发,而对于impure pipe, 当基本类型参数改变或者引用类型内部发生变化都可以触发。

对于上边的例子:

import  {Pipe , PipeTransform} from ‘@angular/core’;
@Pipe({
	name: ‘fullName’,
	pure: true
})
export class FullNamePipe implements PipeTransform {
	transform(person: {firstName: string, lastName: string}, args?: any): string {
	Return person.firstName + ‘ ’ + person.lastName;
}
}

在我们PersonComponent 里使用:

@Component({
	template: `<p> Welcome {{ person | fullName }} !</p>
 <div (mousemove)=”onMouseMove()”> trigger in Person Component</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonComponent {
    
@Input() person: { firstName: string, lastName: stirng };
constructor() { }
get fullName() {
	return this.person.firstName + ‘ ’ + this.person.lastName;
}
onMouseMove() {
	console.log(‘mouse move’);
 }
}

因为person 参数没有改变,所以Angular 就跳过Pipe 里的transform 方法。

2 手动计算值

避免不必要的函数调用,我们可以自己在PersonComponent 组件手动计算所需要的值。因为我们能够知道值是何时改变的。

在上边的示例中,我们可以在person被更改时计算全名,因此我们可以添加一个fullName属性,当ngOnChanges 中改变person输入的值时,重新计算fullName的值。

@Component({
	template: `<p> Welcome {{  fullName }} !</p>
 <div (mousemove)=”onMouseMove()”> trigger in Person Component</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PersonComponent implements OnChanges {
    
@Input() person: { firstName: string, lastName: stirng };
constructor() { }
onMouseMove() {
	console.log(‘mouse move’);
 }
ngOnChanges(changes: SimpleChanges) {
if (changes.person) {
	this.fullName = this.calculateFullName();
}
}

calculateFullName() {
	return this.person.firstName + ‘ ’ this.person.lastName;
}
}

因此只有当组件的person 输入发生改变时才会重新计算fullName.

内容概要:本文介绍了一个基于多传感器融合的定位系统设计方案,采用GPS、里程计和电子罗盘作为定位传感器,利用扩展卡尔曼滤波(EKF)算法对多源传感器数据进行融合处理,最终输出目标的滤波后位置信息,并提供了完整的Matlab代码实现。该方法有效提升了定位精度与稳定性,尤其适用于存在单一传感器误差或信号丢失的复杂环境,如自动驾驶、移动采用GPS、里程计和电子罗盘作为定位传感器,EKF作为多传感器的融合算法,最终输出目标的滤波位置(Matlab代码实现)机器人导航等领域。文中详细阐述了各传感器的数据建模方式、状态转移与观测方程构建,以及EKF算法的具体实现步骤,具有较强的工程实践价值。; 适合人群:具备一定Matlab编程基础,熟悉传感器原理和滤波算法的高校研究生、科研人员及从事自动驾驶、机器人导航等相关领域的工程技术人员。; 使用场景及目标:①学习和掌握多传感器融合的基本理论与实现方法;②应用于移动机器人、无人车、无人机等系统的高精度定位与导航开发;③作为EKF算法在实际工程中应用的教学案例或项目参考; 阅读建议:建议读者结合Matlab代码逐行理解算法实现过程,重点关注状态预测与观测更新模块的设计逻辑,可尝试引入真实传感器数据或仿真噪声环境以验证算法鲁棒性,并进一步拓展至UKF、PF等更高级滤波算法的研究与对比。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值