文章目录
-
-
- 什么是 Angular
- Angular 有什么优势
- Angular 和 jQuery 的区别
- Angular 和 Vue 的区别
- Angular 依赖注入
- Angular Service
- Angular Module
- 数据绑定的方式有哪些
- Angular 双向绑定
- Angular 生命周期
- 父子组件通信方式
- 管道的作用
- Angular 变更检测策略
- 常用到的装饰器
- 数据之间通信有哪些方式
- AOT和JIT
- router 和 route
- Zone是什么
- 如何提高 Angular 的性能
- RxJS是什么?基本概念有哪些
- promise和observable的区别
- RxJS常用的Operators操作符
-
什么是 Angular
Angular 是一个开放源代码的前端 Web 框架。它是最流行的 JavaScript 框架之一,主要由 Google 维护。
它提供了一个轻松开发基于 Web 的应用程序的平台,并使前端开发人员能够管理跨平台应用程序。
它集成了强大的功能,例如声明性模板,依赖项注入以及各种其他使开发路径更流畅的最佳实践。
Angular 通常用于表示单页应用程序的 SPA 的开发。
Angular 提供了一组现成的模块,可简化单页应用程序的开发。
不仅如此,Angular 还具有内置数据流,类型安全性和模块化 CLI 的功能,被认为是成熟的 Web 框架。
Angular 有什么优势
可伸缩性:基于 RxJS 、immutable.js 和其他推送模型,能适应海量数据需求
跨平台:渐进式应用(高性能、离线使用、免安装),原生(Ionic),桌面端
生产率:模版(通过简单而强大的模版语法,快速创建 UI 视图),CLI(快速进入构建环节、添加组件和测试,然后立即部署)
测试:单元测试(支持 Karma、Jasmine 等工具进行单元测试),端到端测试(支持 Protractor 等工具进行端到端测试)
优点:
- 数据双向绑定
- 添加自定义的指令、管道
- 支持表单验证
- 支持依赖注入
- 强大的表单功能
缺点:
- 太过笨重
- 编译速度慢
Angular 和 jQuery 的区别
特征 | jQuery | Angular |
---|---|---|
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 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChangesngOnInit
:组件初始化时被调用,在第一轮 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的区别
observable | promise |
---|---|
计算在订阅之前不会开始,因此它们可以在您需要结果时运行 | 在创建时立刻执行 |
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 秒,延时时间却有两秒,也就是除非发送完,否则不可能满足发送源等待两秒再发送新数据,每次发完新数据之后要等两秒之后才会有打印,所以不论我们该数据源发送多少个数,最终订阅者收到的只有最后一个数。