Angular 2 面试题






4.92/5 (26投票s)
一系列精选的 Angular 2 问题和答案,帮助您理清 Angular 2 的概念
引言
阅读面试题是学习和巩固遗忘概念的绝佳方式,即使您没有准备面试。在本文中,我们尝试触及 Angular 2 的大部分重要概念。我们还提供了进一步阅读的外部链接/参考资料。
免责声明
阅读本文并不能保证您一定能通过 Angular 2 面试。我们的唯一目的是为您提供一个用于临时复习和进一步阅读的参考。
如果您觉得还需要涵盖更多主题,请告知我们。我们会将其添加到文章中。
问题
解释 Angular 2 应用程序的生命周期钩子 (Life Cycle Hooks)
Angular 2 组件/指令具有由 @angular/core
管理的生命周期事件。它创建组件,渲染它,创建并渲染其子组件,当其数据绑定属性发生变化时处理变更,然后将其销毁,再从 DOM 中移除其模板。Angular 提供了一组生命周期钩子(特殊事件),您可以利用这些钩子在这个生命周期中进行操作。构造函数在所有生命周期事件之前执行。每个接口都有一个以 ng
开头的钩子方法。例如,ngOnint
接口有一个 Oninit
方法,必须在组件中实现。
一些事件既适用于组件/指令,也适用于特定组件。
ngOnChanges
:在 Angular 设置其数据绑定属性时响应,接收当前值和前一个值对象。ngOnInit
:在第一次ngOnChange
触发后初始化组件/指令。这是从后端服务检索模板数据最常用的方法。ngDoCheck
:检测并在 Angular 上下文之外发生的更改并采取行动。它在每次变更检测运行时被调用。ngOnDestroy
:在 Angular 销毁指令/组件之前进行清理。取消订阅可观察对象和分离事件处理器以避免内存泄漏。
特定于组件的钩子
ngAfterContentInit
:组件的内容已初始化ngAfterContentChecked
:在 Angular 检查其投影到其视图中的外部内容绑定后ngAfterViewInit
:在 Angular 创建组件的视图后ngAfterViewChecked
:在 Angular 检查组件视图的绑定后
与 Angular 1 相比,使用 Angular 2 的优势是什么?
- Angular 2 是一个平台,不仅仅是一个语言
- 更快的速度和更高的性能:Angular 2 中没有 $Scope,有 AOT
- 更简单的依赖注入
- 模块化,跨平台
- ES6 和 Typescript 的优势
- 具有延迟加载功能的灵活路由
- 更容易学习
Angular 2 中的路由是如何工作的?
路由是一种机制,使用户能够在视图/组件之间导航。Angular 2 简化了路由,并提供了在模块级别配置和定义的灵活性(延迟加载)。
Angular 应用程序只有一个 Router 服务实例,每当 URL 更改时,就会从路由配置数组中匹配相应的路由。成功匹配后,它会应用重定向,然后路由器会构建一个 ActivatedRoute
对象树,该对象包含路由器的当前状态。在重定向之前,路由器会检查新状态是否被守卫(CanActivate)允许。路由守卫本质上是一个接口方法,路由器会运行它来检查路由的授权。守卫运行后,它会解析路由数据,并通过实例化所需的组件到 <router-outlet> </router-outlet>
来激活路由状态。
延伸阅读
- https://codeproject.org.cn/Articles/1164813/Angular-Routing
- https://vsavkin.com/angular-2-router-d9e30599f9ea#.kt4z1v957
什么是事件发射器 (Event Emitters)?它在 Angular 2 中是如何工作的?
与 Angular 1 不同,Angular 2 没有双向脏检查机制。在 Angular 2 中,组件中发生的任何更改都会从当前组件传播到其层次结构中的所有子组件。如果一个组件中的更改需要反映到其层次结构中的任何父组件,我们可以使用 Event Emitter API 发射事件。
简而言之,EventEmitter
是 @angular/core
模块中定义的类,组件和指令可以使用它来发出自定义事件。
@output() somethingChanged = new EventEmitter();
我们使用 somethingChanged.emit(value)
方法来发出事件。这通常在 setter 中完成,当类中的值正在被更改时。
此 emit
事件可以由模块中的任何组件使用 subscribe
方法订阅。
myObj.somethingChanged.subscribe(val) => this.myLocalMethod(val));
延伸阅读
- http://stackoverflow.com/questions/36076700/what-is-the-proper-use-of-an-eventemitter
- https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html
Codelyzer 在 Angular 2 应用程序中的作用是什么?
所有企业应用程序都遵循一套编码约定和准则,以便更好地维护代码。Codelyzer 是一个开源工具,用于运行和检查是否遵循了预定义的编码准则。Codelyzer 只对 Angular 和 TypeScript 项目进行静态代码分析。
Codelyzer 在 tslint 之上运行,其编码约定通常在 tslint.json 文件中定义。Codelyzer 可以通过 Angular cli 或 npm 直接运行。像 Visual Studio Code 和 Atom 这样的编辑器也通过简单的设置支持 codelyzer。
要在 Visual Studio Code 中设置 codelyzer,我们可以转到 **文件 -> 首选项 -> 用户设置**,然后添加 tslint 规则的路径。
{
"tslint.rulesDirectory": "./node_modules/codelyzer",
"typescript.tsdk": "node_modules/typescript/lib"
}
从 cli 运行:ng lint
。
从 npm 运行:npm run lint
延伸阅读
什么是延迟加载 (Lazy Loading)?以及如何在 Angular 2 中启用延迟加载?
大多数企业应用程序包含用于特定业务场景的各种模块。将整个应用程序代码打包加载会在初始调用时产生巨大的性能影响。延迟加载使我们能够仅加载用户正在交互的模块,而将其余部分保留在运行时按需加载。
延迟加载通过将代码分成多个捆绑包并在按需加载它们来加速应用程序的初始加载时间。
每个 Angular 应用程序都必须有一个主模块,例如 AppModule
。代码应根据应用程序的业务场景拆分成多个子模块 (NgModule
)。
Plunkr 示例:链接
- 我们不需要在根模块中导入或声明延迟加载的模块。
- 在顶层路由 (app.routing.ts) 中添加路由并设置
loadChildren
。loadChildren
采用从根文件夹开始的绝对路径,后跟#{ModuleName}
。RouterModule.forRoot()
接受路由数组并配置路由器。 - 在子模块中导入模块特定的路由。
- 在子模块路由中,将路径指定为空字符串 ' ',空路径。
RouterModule.forChild
再次接受路由数组,用于加载子模块组件并为子模块配置路由器。 - 然后,导出
const
路由:ModuleWithProviders = RouterModule.forChild(routes);
Angular 2 应用程序中应警惕哪些安全威胁?
与任何其他客户端或 Web 应用程序一样,Angular 2 应用程序也应遵循一些基本准则来减轻安全风险。其中一些是
- 避免将动态 HTML 内容使用/注入到您的组件中。
- 如果使用来自数据库或应用程序外部的外部 HTML,请对其进行清理。
- 尽量不要在应用程序中放置外部 URL,除非它是受信任的。除非受信任,否则避免 URL 重定向。
- 考虑使用 AOT 编译或离线编译。
- 尝试通过限制 API 和应用程序的使用环境(已知或安全环境/浏览器)来防止 XSRF 攻击。
延伸阅读
如何优化 Angular 2 应用程序以获得更好的性能?
嗯,优化取决于应用程序的类型和大小以及许多其他因素。但总的来说,在优化 Angular 2 应用时,我会考虑以下几点:
- 考虑 AOT 编译。
- 确保应用程序已打包、丑化,并进行了树摇 (tree shaking)。
- 确保应用程序没有不必要的
import
语句。 - 确保应用程序中已移除任何未使用的第三方库。
- 清晰地分离所有依赖项和开发依赖项。
- 如果应用程序的规模较大,我会考虑使用延迟加载而不是完全打包的应用程序。
延伸阅读
- https://medium.com/@areai51/the-4-stages-of-perf-tuning-for-your-angular2-app-922ce5c1b294#.pw4m2srmr
- https://www.lucidchart.com/techblog/2016/05/04/angular-2-best-practices-change-detector-performance/
如何定义自定义类型 (Typings) 以避免编辑器警告?
在大多数情况下,第三方库都带有自己的 .d.ts 文件用于其类型定义。在某些情况下,我们需要通过为其提供更多属性来扩展现有类型,或者需要定义其他类型来避免 TypeScript 警告。
如果我们想为外部库扩展类型定义,作为一项好的做法,我们不应该修改 node_modules
或现有的 typings 文件夹。我们可以创建一个新文件夹,例如“custom-typings”,并将所有自定义类型定义保存在其中。
要为应用程序(JavaScript/TypeScript)对象定义类型,我们应该在应用程序的相应模块的 models 文件夹中定义接口和实体类。
对于这些情况,我们可以通过创建自己的“.d.ts”文件来定义或扩展类型。
延伸阅读
- https://typescript.net.cn/docs/handbook/declaration-merging.html
- https://typescript.codeplex.com/wikipage?title=Writing%20Definition%20%28.d.ts%29%20Files
- http://stackoverflow.com/questions/32948271/extend-interface-defined-in-d-ts-file
什么是 Shadow DOM?它如何帮助 Angular 2 获得更好的性能?
Shadow DOM 是 HTML 规范的一部分,它允许开发人员封装其 HTML 标记、CSS 样式和 JavaScript。Shadow DOM 与其他一些技术一起,使开发人员能够像 <audio>
标签一样构建自己的第一类标签、Web 组件和 API。总的来说,这些新标签和 API 被称为 Web Components。Shadow DOM 提供了更好的关注点分离,以及与 HTML DOM 元素的样式和脚本冲突更少。
由于 Shadow DOM 本质上是静态的,它是一个可以缓存的良好候选者,因为它对开发人员不可访问。缓存的 DOM 将在浏览器中更快地渲染,从而提供更好的性能。此外,在检测 Angular 2 应用程序中的更改时,Shadow DOM 可以得到相对较好的管理,并且视图的重新绘制可以高效地进行管理。
参考/进一步阅读
- https://mdn.org.cn/en-US/docs/Web/Web_Components/Shadow_DOM
- https://glazkov.com/2011/01/14/what-the-heck-is-shadow-dom/
- https://code.tutsplus.com/tutorials/intro-to-shadow-dom--net-34966
什么是 AOT 编译?
AOT 编译是 Ahead Of Time 编译的缩写,其中 Angular 编译器在构建时将 Angular 组件和模板编译成原生 JavaScript 和 HTML。编译后的 HTML 和 JavaScript 被部署到 Web 服务器,以便浏览器可以节省编译和渲染时间。
优点
- 更快的下载:由于应用程序已经编译,许多 Angular 编译器相关的库不需要被打包,应用程序捆绑包的大小会减小。因此,应用程序可以更快地下载。
- 更少的 HTTP 请求:如果应用程序未打包以支持延迟加载(或出于任何原因),则需要为每个关联的 HTML 和 CSS 向服务器发送一个单独的请求。预编译的应用程序将所有模板和样式与组件内联,因此到服务器的 Http 请求数量会更少。
- 更快的渲染:如果应用程序未进行 AOT 编译,则编译过程将在应用程序完全加载后在浏览器中发生。这需要等待所有必要的组件下载,然后是编译器编译应用程序的时间。使用 AOT 编译,这一点得到了优化。
- 在构建时检测错误:由于编译提前发生,可以检测到许多编译时错误,从而提高了应用程序的稳定性。
缺点
- 仅适用于 HTML 和 CSS,其他文件类型需要预先构建步骤
- 尚无监视模式,必须手动执行(bin/ngc-watch.js)并编译所有文件
- 需要维护 AOT 版本的引导文件(在使用 CLI 等工具时可能不需要)
- 编译前需要清理步骤
参考/进一步阅读
Observable 和 Promise 之间的核心区别是什么?
摘自 Stackoverflow 的一个很好的回答
Promise 在异步操作完成或失败时处理 **单个事件**。
注意:有一些 Promise 库支持取消,但 ES6 Promise 目前不支持。
Observable 类似于许多语言中的 **流** (Stream),并允许传递零个或多个事件,其中每个事件都会调用回调。通常,Observable 比 Promise 更受青睐,因为它提供了 Promise 的功能以及更多功能。使用 Observable,无论您想处理 0、1 还是多个事件,都没关系。在每种情况下,您都可以利用相同的 API。Observable 相对于 Promise 的优势还在于它是 **可取消的**。如果不再需要对服务器的 HTTP 请求或其他昂贵的异步操作的结果,Observable 的 Subscription 允许取消订阅,而 Promise 即使您不再需要通知或其提供的结果,最终也会调用成功或失败回调。Observable 提供了类似于数组的 **操作符**,如 map
、forEach
、reduce
等。还有一些强大的操作符,如 retry()
或 replay()
等,这些操作符通常非常有用。
Promises vs Observables
- Promises
- 返回单个值
- 不可取消
- Observables (可观察对象)
- 随时间处理多个值
- 可取消
- 支持 map、filter、reduce 和类似的操作符
- ES 2016 的提议功能
- 使用 Reactive Extensions (RxJS)
- 一个随时间异步到达的数组
参考/进一步阅读
解释局部引用变量 (Local Reference Variables)、ViewChild 和 ContentChild。
Angular2 中的局部模板变量用于引用 HTML 元素并使用其属性来访问兄弟元素或子元素。
假设您有一个名为 username
的输入字段。
<input type="text" required ... />
此 HTMLInputField
可以使用变量名(例如 username)前的 #
符号使其在模板中可用。
<input type="text" #username required ... />
现在,此 HTMLInputElement
可以在当前模板的任何地方访问,例如,进行验证并根据验证规则显示适当的消息。但是,username HTML 引用在组件/指令中不可访问。
要访问组件中的此项,Angular 2 提供了 @ViewChild
装饰器,该装饰器接受局部引用变量。
@ViewChild('username') username: HTMLInputElement;
ViewChild
元素可以在视图初始化后(ngAfterViewInit
)读取。
ContentChild
用于查询 ng-content
内的 DOM 引用。Content Child 在 ngAfterContentInit
生命周期钩子之前设置。
例如
// <code>app.component.ts</code>
<my-component>
<p #contentRef>{{test}}</p>
</ my-component >
// MyComponent.component.ts
@Component({
selector: ‘my-component',
template: `
<ng-content></ng-content>
<div> ContentChild Example </div>
})
export class LifecycleComponent implements ngAfterContentInit{
@ContentChild(‘contentRef’) childContent: HTMLElement;
ngAfterContentInit() {
this.log('ngAfterContentInit');
console.log(this.childContent);
}
}
延伸阅读
关注点
您认为这个列表不足以帮助您复习 Angular 2 吗?我们也这么认为。我们正在利用空闲时间增加更多内容。请关注本文以获取更新。您也可以建议应在此处添加哪些问题。
历史
- 2017-02-11:发布第一个版本
- 2017-03-04:添加了更多问题