angular学习笔记

常见内容

预编译 (ahead-of-time, AoT)

在打包项目的时候提前编译好应用,打包好之后可以直接启动,而不是把编译器打包在应用中用的时候再编译。生产环境使用。

Angular 应用主要由组件及其 HTML 模板组成。由于浏览器无法直接理解 Angular 所提供的组件和模板,因此 Angular 应用程序需要先进行编译才能在浏览器中运行。Angular 预先(AOT)编译器会先把你的 Angular HTML 和 TypeScript 代码转换成高效的 JavaScript 代码。 在构建期间编译应用可以让浏览器中的渲染更快速。

使用 AOT 的部分原因:

  • 更快的渲染。 借助AOT,浏览器可以下载应用的预编译版本。浏览器加载的是可执行代码,因此它可以立即呈现应用,而无需等待先编译好应用。

  • 更少的异步请求。 编译器会在应用 JavaScript 中内外部 HTML 模板和 CSS 样式表,从而消除了对那些源文件的单独 ajax 请求。

  • 较小的 Angular 框架下载大小。 如果已编译应用程序,则无需下载 Angular 编译器。编译器大约是 Angular 本身的一半,因此省略编译器会大大减少应用程序的有效载荷。

  • 尽早检测模板错误。 AOT 编译器会在构建步骤中检测并报告模板绑定错误,然后用户才能看到它们。

  • 更高的安全性。 AOT 在将 HTML 模板和组件提供给客户端之前就将其编译为 JavaScript 文件。没有要读取的模板,没有潜藏风险的客户端 HTML 或 JavaScript eval,受到注入攻击的机会就更少了。

AOT 编译分为三个阶段。

  • 阶段 1 是代码分析 。 在此阶段,TypeScript 编译器和 AOT 收集器会创建源码的表现层。收集器不会尝试解释其收集到的元数据。它只是尽可能地表达元数据,并在检测到元数据语法冲突时记录错误。

  • 第二阶段是代码生成。 在此阶段,编译器的 StaticReflector 会解释在阶段 1 中收集的元数据,对元数据执行附加验证,如果检测到元数据违反了限制,则抛出错误。

  • 阶段 3 是模板类型检查。在此可选阶段,Angular 模板编译器使用 TypeScript 编译器来验证模板中的绑定表达式。您可以通过设置 fullTemplateTypeCheck 配置选项来明确启用此阶段。请参阅 Angular 编译器选项

即时编译 (just-in-time, JiT)

浏览器中启动并编译所有的组件和模块,动态运行应用程序。开发过程中使用。

依赖注入(Dependency Injection)

组件之间的依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

Change Detection (变化检测)

Change Detection (变化检测) 是 Angular 2 中最重要的一个特性。当组件中的数据发生变化的时候,Angular 2 能检测到数据变化并自动刷新视图反映出相应的变化。

而引起模型变化的三类事件源:

  • Events:click, mouseover, keyup …
  • Timers:setInterval、setTimeout
  • XHRs:Ajax(GET、POST …)

Zones

Zone 是一个全局的对象,用来配置有关如何拦截和跟踪异步回调的规则。Zone 有以下能力:

  • 拦截异步任务调度
  • 提供了将数据附加到 zones 的方法
  • 为异常处理函数提供正确的上下文
  • 拦截阻塞的方法,如 alert、confirm 方法

在 Angular 2 中,任何数据都是从顶部往底部流动,即单向数据流。下图是 Angular 1.x 与 Angular 2 变化检测的对比图:

WX20200215-194649@2x

可以通过组件生命周期提供的钩子 ngOnChanges 来捕获变化的内容,该对象内包含了 previousValue (之前的值) 和 currentValue (当前值)。

WX20200215-194854@2x

ng2.x和ng1变化检测对比

ng1 ng2
触发时机 1. 调用angular封装的DOM事件(click) 2. 调用http服务(\$http) 3. 调用定时器(\$timeout,\$interval) 4. 手动调用\$scope.\$apply或\$scope.digest 1. Events:click, mouseover, keyup … 2. Timers:setInterval、setTimeout 3.XHRs: Ajax(GET、POST …)
脏检查 AngularJS采用的是双向数据流,错综复杂的数据流使得它不得不多次检查,使得数据最终趋向稳定。 遍历监视器队列中的每一个监控元素,如果监控元素发生了变化,则调用其注册的监听函数,在监听函数中会改变相应视图元素 两层循环,如果值一直是 dirty 就会持续循环检查,直到值稳定 Angular的数据流是自顶而下,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测。尽管检查了父组件之后,子组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError(关于这个问题的答案,可以在参考资料中找到)。而在生产环境中,脏检查只会执行一次。 不再遍历所有变量,而是通过父组件筛选变动的子组件,尽量向下查找

onPush策略与Default策略

Default :ng提供的Default的检测策略,只要组件的input发生改变,就触发检测;
OnPush :OnPush检测策略是input发生改变,并不立即触发检测,而是输入的引用发生变化时,才会触发检测。普通的string num 等修改就是修改引用,数组和对象 修改其中的值引用并没有发生变化。

当使用 OnPush 策略的时候,若输入属性没有发生变化,组件的变化检测将会被跳过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Component } from '@angular/core';

@Component({
selector: 'exe-app',
template: `
<profile-card [profile]='profile'></profile-card>
`
})
export class AppComponent {
profile: { name: string; age: number } = {
name: 'Semlinker',
age: 31
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';

@Component({
selector: 'profile-card',
template: `
<div>
<profile-name [name]='profile.name'></profile-name>
<profile-age [age]='profile.age'></profile-age>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush // onpush
})
export class ProfileCardComponent {
@Input() profile: { name: string; age: number };
}
1
2
3
4
5
6
7
8
9
10
11
import { Component, Input } from '@angular/core';

@Component({
selector: 'profile-age',
template: `
<p>Age: {{age}}</p>
`
})
export class ProfileAgeComponent {
@Input() age: number;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Component, Input } from '@angular/core';

@Component({
selector: 'profile-card',
template: `
<div>
<profile-name [name]='profile.name'></profile-name>
<profile-age [age]='profile.age'></profile-age>
</div>
`
})
export class ProfileCardComponent {
@Input() profile: { name: string; age: number };
}

WX20200215-215258@2x

修改 profile.name 的时候没有更新视图

WX20200215-215807@2x

OnPush 策略是提高应用程序性能的一个简单而好用的方法。不过,我们还有其他方法来获得更好的性能。 即使用 Observable 与 ChangeDetectorRef 对象提供的 API,来手动控制组件的变化检测行为。

ChangeDetectorRef

ChangeDetectorRef 是组件的变化检测器的引用

1
2
3
4
5
import { ChangeDetectorRef } from '@angular/core';

@Component({}) class MyComponent {
constructor(private cdRef: ChangeDetectorRef) {}
}

ChangeDetectorRef 变化检测类中主要方法有以下几个:

1
2
3
4
5
6
export abstract class ChangeDetectorRef {
abstract markForCheck(): void;
abstract detach(): void;
abstract detectChanges(): void;
abstract reattach(): void;
}

其中各个方法的功能介绍如下:

  • markForCheck() - 在组件的 metadata 中如果设置了 changeDetection: ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法。
  • detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。
  • reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测
  • detectChanges() - 从该组件到各个子组件执行一次变化检测

双向绑定