什么是 Angular

Angular 是一个开放源代码的前端 Web 框架。它是最流行的 JavaScript 框架之一,主要由 Google 维护。

它提供了一个轻松开发基于 Web 的应用程序的平台,并使前端开发人员能够管理跨平台应用程序。

它集成了强大的功能,例如声明性模板,依赖项注入以及各种其他使开发路径更流畅的最佳实践。

Angular 通常用于表示单页应用程序的 SPA 的开发。

Angular 提供了一组现成的模块,可简化单页应用程序的开发。

不仅如此,Angular 还具有内置数据流,类型安全性和模块化 CLI 的功能,被认为是成熟的 Web 框架。


Angular 有什么优势

可伸缩性:基于 RxJS 、immutable.js 和其他推送模型,能适应海量数据需求

跨平台:渐进式应用(高性能、离线使用、免安装),原生(Ionic),桌面端

生产率:模版(通过简单而强大的模版语法,快速创建 UI 视图),CLI(快速进入构建环节、添加组件和测试,然后立即部署)

测试:单元测试(支持 Karma、Jasmine 等工具进行单元测试),端到端测试(支持 Protractor 等工具进行端到端测试)

优点:

  1. 数据双向绑定
  2. 添加自定义的指令、管道
  3. 支持表单验证
  4. 支持依赖注入
  5. 强大的表单功能

缺点:

  1. 太过笨重
  2. 编译速度慢

Angular 和 jQuery 的区别

特征jQueryAngular
DOM 操作
RESTful API没有
动画支持
深层链接路由没有
表格验证没有
双向数据绑定没有
AJAX / JSONP

Angular 和 Vue 的区别

  • vue和angular都可以进行动态绑定,例如动态绑定样式,绑定数据,条件式渲染等。vue中的动态绑定都以v开头,而angular以ng开头
  • Vue中冒号相当于angular中的中括号,可以单向绑定动态的值
  • angular中的属性型指令,ngclass,ngstyle,对应到vue中就是:class, :style,ngif, ngswitchcase, v-if v-else-if
  • 都有依赖注入的概念,angular是注入service,注入的是功能所需的服务或者对象,此时组件会从外部源请求依赖项而不是创建他们,service中一般都会存放一些抽离于视图,只处理业务的一些通用逻辑,通过provider提供,直接注入进构造器中。而vue是为了解决props逐级透传问题而提出的依赖注入
  • 都是以html为模板,而不是像react一样嵌套着js写,不同:angular是html,css,ts解耦的写法,也就是数据,视图,样式分离的写法,而vue是单文件类型的写法
  • angular的写法相当于vue3中的组合式api,因为方法,属性等都集中在一起,不需要像vue2的选项式api一样反复来回查看不同地方的代码,因此研发效率高
  • angular中ts是必选项,而vue中是可选项

Angular 依赖注入

Dependency Injection(DI)是一种设计模式,在这种设计模式中,类会从外部源请求依赖项而不是创建它们。

在项目中,有人提供服务,有人消耗服务,而依赖注入的机制提供了中间的接口,并替消费者创建并初始化处理。

消费者只需要知道拿到的是完整可用的服务就好,至于这个服务内部的实现,甚至是它又依赖了怎样的其他服务,都不需要关注。

Angular 通过 service 共享状态,而这些管理状态和数据的服务便是通过依赖注入的方式进行处理的。

Angular 的 service 的本质就是依赖注入,将 service 作为一个 Injector 注入到 component 中

很多时候我们创建服务就是为了维护公用的状态和数据,通过依赖注入的方式来规定哪些组件可共享。

优点:

  • 松耦合,增加了代码的可扩展性
  • 使代码可重用性更高

控制反转定义(IOC)

控制什么?

控制外部的一个资源获取

反转是什么?

我们需要知道什么是正转,正转就是通过new主动去创建一个对象。

反转:由反转容器去创建控制对象,将控制权从代码内部转入代码外部,这就是控制反转。

控制反转的手段叫做依赖注入,控制反转是目的。

传统应用程序都是由我们在类内部主动创建依赖对象,也就是所谓的new。

从而导致类与类之间高耦合,难于测试;

有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活

实现控制反转的容器叫做 ioc 容器,angular 就是 ioc 容器


Angular Service

服务(Service)充当着数据访问,逻辑处理的功能。

好处:把组件和服务区分开,保持业务逻辑模块组件内的纯净和高内聚,以提高模块性和复用性。

单例服务(singleton)

使用 Angular CLI 创建服务,默认会创建单例服务;

把 @Injectable () 的 providedIn 属性声明为 root, 即为单例服务。

单例服务(singleton)对象,可以用于临时存放全局变量。

对于复杂的全局变量,推荐使用状态管理组件(state management - Ngrx)。

forRoot () 模式

如果多个调用模块同时定义了 providers (服务),那么在多个特性模块中加载此模块时,这些服务就会被注册在多个地方。

这会导致出现多个服务实例,并且该服务的行为不再像单例一样 。有多种方式来防止这种现象:

  • 用 providedIn 语法代替在模块中注册服务的方式。
  • 把服务分离到它们自己的模块中。
  • 在模块中分别定义 forRoot () 和 forChild () 方法。

Angular Module

定义:相当于应用程序中的边界,用来分离应用程序之间的功能。

imports :属性告诉angular需要导入加载哪些模块

declarations:属性用于声明本模块中拥有的视图类,Angular有三种视图类:组件、指令和管道

providers:表明该模块可以提供的服务,管道等

bootstrap:当这个模块被引导时被引导的一组组件。这里列出的组件会自动添加到entryComponents中。告诉angular根组件是什么

entrycomponent:定义要编译的组件集,这样它们才可以动态加载到视图中


数据绑定的方式有哪些

  • 字符串插值(花括号绑定)
  • 属性绑定(单向绑定)
  • 事件绑定(output事件绑定)
  • 双向数据绑定(相当于属性绑定和事件绑定结合的语法糖)

Angular 双向绑定

单向数据绑定中,无论何时更改数据模型,“视图” 或 “ UI” 部分都不会自动更新。需要手动编写自定义代码,以便在每次视图更改时对其进行更新。

而在双向数据绑定中,一旦更改数据模型,则隐式更新 View 或 UI 部分。与单向数据绑定不同,这是一个同步过程。

Angular 的双向绑定,通过脏数据检查(Dirty checking)来实现。

脏值检测的基本原理是存储旧数值,并在进行检测时,把当前时刻的新值和旧值比对。

若相等则没有变化,反之则检测到变化,需要更新视图。


Angular 生命周期

生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法

当 Angular 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子:

  • constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化
  • ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges
  • ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建
  • ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后
  • ngAfterContentInit :内容投影 ng-content 完成时调用,只在第一次 ngDoCheck 之后调用
  • ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)
  • ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次
  • ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)
  • ngOnDestroy :当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏

父子组件初始化时生命周期钩子调用顺序:

parent-ngOnChanges
parent-ngOnInit
parent-ngDoCheck
parent-ngAfterContent
parent-ngAfterContentked

child-ngChanges
child-ngOnInit
child-ngDoCheck
child-ngAfterContentInit
child-ngAfterContentChecked
child-ngAfterViewInit
child-ngAfterViewChecked

parent-ngAfterViewInit
parent-ngAfterViewChecked

再次检测时

parent-ngDoCheck
parent-ngAfterContentked
child-ngDoCheck
child-ngAfterContentChecked
child-ngAfterViewChecked
parent-ngAfterViewChecked


父子组件通信方式

  • 装饰器 input/output
  • 通过 service

管道的作用

Angular 管道是编写可以在 HTML 组件中声明的显示值转换的方法

管道将数据作为输入并将其转换为所需的输出

管道其实就是过滤器,用来转换数据然后显示给用户

管道将整数、字符串、数组和日期作为输入,用 | 分隔,然后根据需要转换格式,并在浏览器中显示出来


Angular 变更检测策略

Angular 有两种变更检测策略:Default 和 OnPush

可以通过在 @Component 元数据中设置 changeDetection: ChangeDetectionStrategy.OnPush 进行切换

Default:

优点:每一次有异步事件发生,Angular 都会触发变更检测,从根组件开始遍历其子组件,对每一个组件都进行变更检测,对 dom 进行更新。

缺点:有很多组件状态没有发生变化,无需进行变更检测。如果应用程序中组件越多,性能问题会越来越明显。

OnPush:

优点:组件的变更检测完全依赖于组件的输入 (@Input),只要输入值不变就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升。

缺点:必须保证输入 (@Input) 是不可变的 (可以用 Immutable.js 解决),每一次输入变化都必须是新的引用。


常用到的装饰器

  • component 组件
  • directive 指令
  • pipe 管道
  • ngModule 模块
  • input 父传子 输入
  • output 子传父 传出

数据之间通信有哪些方式

  • 使用事件
  • 使用服务
  • 使用继承
  • 使用rxjs

AOT和JIT

AOT:ahead of time预编译

JIT: just in time即时编译

在Angular 8之前,JIT编译是默认的,现在默认是AOT。

当运行ng build(只构建)或ng serve(本地构建和服务)CLI命令时,编译的类型(JIT或AOT)取决于angular.json中构建配置中指定的AOT属性的值。缺省情况下,aot为true。

AOT的好处:

  • 更快的渲染速度:浏览器下载应用程序的预编译版本。因此它可以立即呈现应用程序而无需编译应用程序。
  • 更小的下载体积:不需要下载 Angular 编译器。因此,它大大减少了应用程序的有效负载。
  • 更早的检测模板错误
  • 更加安全:它将 HTML 模板和组件编译成 JavaScript。所以不会有任何注入攻击。

router 和 route

router: 从一个页面跳转到另一个页面的机制,里面有router-link、router-outlet

route: 一个路由器,定义了路由跳转规则,路由器必须配置有路由定义列表。可以通过 RouterModule.forRoot() 方法使用路由配置路由器,并将结果添加到 AppModule 的导入数组


Zone是什么

Zone 是跨异步任务持续存在的执行上下文。

当本机 JavaScript 操作引发事件时,Angular 依赖于 zone.js 来运行 Angular 的变更检测


如何提高 Angular 的性能

Angular 也还是网页应用,所以一般的提高网页西能的技巧都是通用的。

针对 Angular,还有一些特殊的优化技巧:

  • AOT 编译
  • 去掉不必要的 import 语句。如果有遗留,那么打包时也会打进来。
  • 确保应用中已经移除了不使用的第三方库
  • 项目较大时,考虑延迟载入 (Lazy Loading), 保证首页的加载速度
const routes: Routes = [
  {
    path: 'customers',
    loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule)
  }
];

RxJS是什么?基本概念有哪些

RxJS 本质是个工具库,处理的是事件,这里的 events 可以称之为流

RxJS 结合了函数式编程、观察者模式(例如 DOM EventListener)、迭代器模式(例如 ES6 Iterater)

那么流是指什么呢?

举个例子,代码中每 1s 输出一个数字,用户每一次对元素的点击,就像是在时间这个维度上,产生了一个数据集。这个数据集不像数组那样,它不是一开始都存在的,而是随着时间的流逝,数据一个一个被输出。这种异步行为产生的数据,就可以被称之为一个流。

在 RxJS 中,称之为 ovservalbe(抛开英文,本质其实就是一个数据的集合,只是这些数据不一定是一开始就设定好的,而是随着时间而不断产生的)

而 RxJS,就是为了处理这种流而产生的工具,比如流与流的合并、流的截断、延迟、消抖等

基本概念

  • Observable: 相当于是数据源,一个可观察对象
  • Subscribe: 相当于去订阅监听这个数据源的流动
  • Observer: 观察者,相当于一个哨兵,推送通知的消费者的接口,里面有next,error,complete(单播,只会推送一次)。从行为上来看,无非就是定义了如何处理上述流产生的数据,称之为流的处理方法。
  • Subscription:就是表示Observable的执行,它的本质就是暂存了一个启动后的流,每一个启动后的流都是相互独立的,而这个启动后的流,就存储在 subscription 中,提供了 unsubscribe方法,来停止这个流。
  • subject: 它是一个代理对象,既是一个 Observable 又是一个 Observer,它可以同时接受 Observable 发射出的数据,也可以向订阅了它的 observer 发射数据。同时,Subject 会对内部的 observers 清单进行多播 (multicast),Subjects 是将任意 Observable 执行共享给多个观察者的唯一方式

从数据的角度来思考:

observable 定义了要生成一个什么样的数据

其 subscribe 方法接收一个 observer(定义了接收到数据如何处理),并开始产生数据

该方法的返回值 subscription, 存储了这个已经开启的流,同时具有 unscbscribe 方法,可以将这个流停止

整理成这个公式:

Subscription = Observable.subscribe (observer)

observable: 随着时间产生的数据集合,可以理解为流,其 subscribe 方法可以启动该流

observer: 决定如何处理数据

subscription: 存储已经启动过的流,其 unsubscribe 方法可以停止该流


promise和observable的区别

observablepromise
计算在订阅之前不会开始,因此它们可以在您需要结果时运行在创建时立刻执行
subscribe返回可以有多个值then返回只能有一个值
订阅方法中可以进行错误处理,使错误处理集中且可预测将错误推送给子promise
Observable 可以取消Promise 不可以取消

RxJS常用的Operators操作符

  • from

从一个数组、类数组对象、Promise、迭代器对象或者类 Observable 对象创建一个 Observable。该方法就有点像 js 中的 Array.from 方法(可以从一个类数组或者可迭代对象创建一个新的数组),只不过在 RxJS 中是转成一个 Observable 给使用者使用。

const source = Rx.Observable.from([10, 20, 30]);
source.subscribe(v => console.log(v));

// 10
// 20
// 30
  • of

与 from 的能力差不多,只不过在使用的时候是传入一个一个参数来调用的,有点类似于 js 中的 concat 方法。同样也会返回一个 Observable,它会依次将你传入的参数合并并将数据以同步的方式发出。

const source = Rx.Observable.of(1, 2, 3);
source.subscribe(v => console.log(v));

// 1
// 2
// 3
  • take

只发出源 Observable 最初发出的 N 个值 (N = count)

用于控制只获取特定数目的值,跟 interval 这种会持续发送数据的配合起来就能自主控制要多少个值了。

  • skip

返回一个 Observable, 该 Observable 跳过源 Observable 发出的前 N 个值 (N = count)。

假设这个数据源发送 6 个值,可以使用 skip 操作符来跳过前多少个。

const source = Rx.Observable.from([1, 2, 3, 2, 4, 3]);
const result = source.skip(2);
result.subscribe(x => console.log(x));

// 打印结果为:3、2、4、3,跳过了前面两个数。
  • debounceTime

功能与 debounce 防抖函数差不多,只有在特定的一段时间经过后并且没有发出另一个源值,才从源 Observable 中发出一个值。

假设一个数据源每隔一秒发送一个数,而我们使用了 debounceTime 操作符,并设置了延时时间,那么在数据源发送一个新数据之后,如果在延时时间内数据源又发送了一个新数据,这个新的数据就会被先缓存住不会发送,等待发送完数据之后并等待延时时间结束才会发送给订阅者。不仅如此,在延时时间未到的时候并且已有一个值在缓冲区,这个时候又收到一个新值,那么缓冲区就会把老的数据抛弃放入新的,然后重新等待延时时间到达然后将其发送。

const source = Rx.Observable.interval(1000).take(3);
const result = source.debounceTime(2000);
result.subscribe(x => console.log(x));

// 程序启动之后的前三秒没有数据打印,等到五秒到了之后,打印出一个2,接着就没有再打印了
// 数据源会每秒依次发送三个数 0、1、2,由于我们设定了延时时间为 2 秒,那么也就是说,我们在数据发送完成之前都是不可能看到数据的,因为发送源的发送频率为 1 秒,延时时间却有两秒,也就是除非发送完,否则不可能满足发送源等待两秒再发送新数据,每次发完新数据之后要等两秒之后才会有打印,所以不论我们该数据源发送多少个数,最终订阅者收到的只有最后一个数。
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐