|
@@ -0,0 +1,166 @@
|
|
|
+好的,我们来一步步拆解“框架”是什么,特别是Angular框架,尽量通俗易懂,并深入其底层原理。
|
|
|
+
|
|
|
+## 一、框架是什么?用生活比喻理解
|
|
|
+
|
|
|
+想象一下你要建房子:
|
|
|
+
|
|
|
+1. **原始方式 (只用 HTML, CSS, JS/TS):**
|
|
|
+ * 你从零开始:挖地基、烧砖头、砍木头、做门窗、铺水管、拉电线...
|
|
|
+ * 你需要自己解决所有问题:结构怎么搭才稳固?门窗怎么装才标准?水电怎么走才安全?
|
|
|
+ * **优点:** 完全自由,想怎么建就怎么建(理论上)。
|
|
|
+ * **缺点:** 极其耗时费力,重复造轮子(每次建房都要从头烧砖砍树),容易出错(结构不稳、水电隐患),不同人建的风格迥异难以维护。
|
|
|
+
|
|
|
+2. **使用框架 (如 Angular):**
|
|
|
+ * 框架就像一套**现代化的预制房屋建造系统**:
|
|
|
+ * **预制构件:** 给你提供标准化的墙板、梁柱、门窗框、水电接口模块(对应 Angular 的 `Component`, `Service`, `Directive`, `Pipe`, `Module`)。
|
|
|
+ * **设计蓝图:** 告诉你房子应该分成几个功能区(客厅、卧室、厨房 - 对应模块化),构件之间如何连接(依赖注入),水电管道如何规范铺设(数据绑定)。
|
|
|
+ * **施工工具:** 提供吊车(CLI)、自动焊接机(编译器)、质检流程(测试工具)。
|
|
|
+ * **施工规范:** 要求你按照特定的方式(如 TypeScript、装饰器语法)来使用这些构件和工具。
|
|
|
+ * **优点:**
|
|
|
+ * **高效:** 不用从烧砖砍树开始,直接用预制件组装,大大加快建造速度(开发效率)。
|
|
|
+ * **一致:** 大家遵循同一套蓝图和规范,建出来的房子结构清晰、风格统一,后期维修(维护)容易。
|
|
|
+ * **可靠:** 预制件和连接方式都经过严格测试,减少了结构隐患(常见 Bug)。
|
|
|
+ * **可扩展:** 系统设计时就考虑了如何添加新房间或升级设施(应用扩展)。
|
|
|
+ * **专注核心:** 你不用操心砖头怎么烧、梁怎么算承重,可以专注于房子的独特设计和功能(业务逻辑)。
|
|
|
+ * **缺点:**
|
|
|
+ * **学习曲线:** 你需要先学会这套系统的规则、构件用法和工具操作(学习框架本身)。
|
|
|
+ * **灵活性限制:** 你必须按照框架规定的方式建造,有些非常特殊的定制需求实现起来可能不如原生灵活(有时需要“绕道”)。
|
|
|
+ * **体积开销:** 这套系统本身有一定的体积(框架代码大小),对于极其简单的小窝棚(微型页面)可能显得有点重。
|
|
|
+
|
|
|
+**总结框架:** **框架就是一套预先定义好的规则、工具和可复用的代码块(“积木”)的集合。它强制或强烈建议你按照它的方式组织代码和构建应用,目的是为了提高开发效率、代码质量、可维护性和团队协作的一致性。** 它帮你处理了大量重复、复杂且容易出错的底层细节(如 DOM 操作、状态同步、路由管理、HTTP 请求封装等)。
|
|
|
+
|
|
|
+## 二、Angular 框架 vs. 常规 HTML/TS:具体区别
|
|
|
+
|
|
|
+假设我们要构建一个显示用户列表的小应用。
|
|
|
+
|
|
|
+1. **常规 HTML/TS (原生方式):**
|
|
|
+ * **HTML (`index.html`):** 主要是一个空的 `<div id="app">`,或者一些静态结构。
|
|
|
+ * **TS (`app.js`):**
|
|
|
+ * 手动用 `document.getElementById` 或 `document.querySelector` 找到 DOM 元素。
|
|
|
+ * 手动发起 `fetch` 或 `XMLHttpRequest` 获取用户数据。
|
|
|
+ * 手动解析数据,遍历用户数组。
|
|
|
+ * 为每个用户手动创建 `div` 或 `li` 元素。
|
|
|
+ * 手动设置元素的内容(`innerHTML` 或 `textContent`)。
|
|
|
+ * 手动给元素添加类名、样式或事件监听器(如点击事件)。
|
|
|
+ * 手动将这些创建好的元素 `appendChild` 到 `#app` 中。
|
|
|
+ * 当数据变化时,需要手动找到对应的 DOM 元素并更新它,或者干脆全部清空重建。
|
|
|
+ * **痛点:**
|
|
|
+ * **代码冗长繁琐:** 大量 DOM 操作代码。
|
|
|
+ * **易出错:** 手动操作 DOM 容易导致内存泄漏、更新不一致等问题。
|
|
|
+ * **难以维护:** 视图逻辑(创建/更新 DOM)和数据逻辑(获取/处理数据)严重混杂在一起。改动一处可能牵一发而动全身。
|
|
|
+ * **缺乏结构:** 随着应用变大,代码会变成难以理解的“意大利面条式”代码。
|
|
|
+ * **重复劳动:** 每次需要显示列表都要写类似的循环创建 DOM 的代码。
|
|
|
+
|
|
|
+2. **Angular 方式:**
|
|
|
+ * **组件 (`user-list.component.ts`):**
|
|
|
+ ```typescript
|
|
|
+ import { Component, OnInit } from '@angular/core';
|
|
|
+ import { UserService } from './user.service'; // 引入服务
|
|
|
+ import { User } from './user.model'; // 引入数据模型
|
|
|
+
|
|
|
+ @Component({
|
|
|
+ selector: 'app-user-list', // 自定义HTML标签 <app-user-list>
|
|
|
+ templateUrl: './user-list.component.html', // 关联的HTML模板
|
|
|
+ styleUrls: ['./user-list.component.css'] // 关联的CSS
|
|
|
+ })
|
|
|
+ export class UserListComponent implements OnInit {
|
|
|
+ users: User[] = []; // 组件内部的数据(状态)
|
|
|
+
|
|
|
+ // 通过依赖注入获得UserService实例
|
|
|
+ constructor(private userService: UserService) {}
|
|
|
+
|
|
|
+ ngOnInit(): void {
|
|
|
+ // 组件初始化时加载数据
|
|
|
+ this.loadUsers();
|
|
|
+ }
|
|
|
+
|
|
|
+ loadUsers(): void {
|
|
|
+ // 使用服务获取数据,更新组件的users属性
|
|
|
+ this.userService.getUsers().subscribe(
|
|
|
+ (users: User[]) => this.users = users,
|
|
|
+ (error) => console.error('Error loading users', error)
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+ * **模板 (`user-list.component.html`):**
|
|
|
+ ```html
|
|
|
+ <h2>User List</h2>
|
|
|
+ <ul>
|
|
|
+ <li *ngFor="let user of users"> <!-- Angular指令:循环users数组 -->
|
|
|
+ {{ user.name }} - {{ user.email }} <!-- 数据绑定:显示用户属性 -->
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ <button (click)="loadUsers()">Reload</button> <!-- 事件绑定:点击调用组件方法 -->
|
|
|
+ ```
|
|
|
+ * **服务 (`user.service.ts`):**
|
|
|
+ ```typescript
|
|
|
+ import { Injectable } from '@angular/core';
|
|
|
+ import { HttpClient } from '@angular/common/http'; // Angular的HTTP客户端
|
|
|
+ import { Observable } from 'rxjs';
|
|
|
+ import { User } from './user.model';
|
|
|
+
|
|
|
+ @Injectable({ providedIn: 'root' }) // 声明为可注入的服务,通常是单例
|
|
|
+ export class UserService {
|
|
|
+ private apiUrl = 'https://api.example.com/users';
|
|
|
+
|
|
|
+ constructor(private http: HttpClient) {} // 注入HttpClient
|
|
|
+
|
|
|
+ getUsers(): Observable<User[]> {
|
|
|
+ return this.http.get<User[]>(this.apiUrl); // 发起HTTP GET请求
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+ * **区别与帮助:**
|
|
|
+ * **组件化:** UI 被拆分成独立的、可复用的组件(`UserListComponent`)。每个组件有自己的模板、逻辑(TS)和样式。
|
|
|
+ * **数据驱动视图:** 组件 TS 中定义数据 (`users` 数组)。模板通过声明式语法 (`*ngFor`, `{{ }}`) **绑定**到这些数据。当 `users` 数据改变(比如 `loadUsers()` 获取到新数据),Angular **自动**更新 DOM 中的列表。你几乎不需要手动操作 DOM!
|
|
|
+ * **关注点分离:**
|
|
|
+ * 组件只关心**如何展示数据**和**处理用户交互**(模板和组件类)。
|
|
|
+ * 服务负责**获取和处理数据**、业务逻辑、与后端通信(`UserService` 使用 `HttpClient`)。
|
|
|
+ * 模型 (`User`) 定义数据结构。
|
|
|
+ * **依赖注入 (DI):** Angular 的 DI 系统自动创建和管理服务(如 `UserService`, `HttpClient`)的实例,并在组件需要时(通过构造函数 `constructor(private userService: UserService)`)**注入**给它们。这使代码更解耦、更易测试(可以轻松替换模拟服务进行测试)。
|
|
|
+ * **声明式编程:** 模板告诉 Angular **“你想要什么”**(显示一个用户列表,每个列表项显示用户的名字和邮箱),而不是 **“如何一步步去做”**(创建元素、设置内容、添加节点)。框架负责实现细节。
|
|
|
+ * **内置强大功能:** 路由、表单处理、HTTP 客户端、国际化、动画等都有官方、统一的解决方案,不需要自己拼凑第三方库。
|
|
|
+ * **工具链:** Angular CLI 提供项目创建、构建、开发服务器、测试、打包等一站式命令,极大提升工程效率。
|
|
|
+ * **结构化和可维护性:** 强制/鼓励模块化、组件化、服务化、单向数据流等最佳实践,使得大型应用结构清晰,代码更易理解、测试和维护。
|
|
|
+
|
|
|
+## 三、Angular 项目的底层原理:魔法是如何发生的?
|
|
|
+
|
|
|
+Angular 的核心魔法在于它的**编译时**和**运行时**机制:
|
|
|
+
|
|
|
+1. **编译时 (Compilation - `ngc` / Ivy Compiler):**
|
|
|
+ * **模板编译:** 这是最关键的一步。当你运行 `ng build` 或 `ng serve` 时,Angular 编译器 (`ngc`) 会处理你的组件模板 (`*.component.html`)。
|
|
|
+ * **从 HTML 到指令:** 编译器解析模板中的特殊语法(如 `*ngFor`, `{{ }}`, `(click)`, `[property]`),将它们转换成 TypeScript 代码。这些生成的代码称为 **工厂函数**。
|
|
|
+ * **创建视图定义:** 工厂函数知道如何动态地创建和更新这个组件对应的 **视图**。视图是 Angular 内部用来表示组件渲染后结构的一个抽象概念,它包含了对 DOM 节点的引用以及如何更新它们的指令。
|
|
|
+ * **增量 DOM 策略:** Angular 的 Ivy 编译器采用 **增量 DOM** 策略。它生成的指令代码精确地描述了 **从当前视图状态到新视图状态需要做的最小变更集**(比如:插入一个节点、删除一个节点、更新一个文本内容、设置一个属性)。这比传统的 Virtual DOM diffing 算法(如 React)在某些场景下更高效,因为它避免了生成整个虚拟树进行 diff 的开销,直接操作真实 DOM 的路径更清晰。
|
|
|
+ * **元数据提取:** 编译器还会读取组件类上的装饰器 (`@Component`, `@Input`, `@Output`),提取元数据信息(选择器、模板 URL、输入输出属性等)。
|
|
|
+ * **结果:** 最终,你的组件类 `.ts` 文件和它的模板 `.html` 被编译(和可能内联的样式 `.css`)一起,生成了优化后的 JavaScript 代码(视图工厂、组件定义等),这些代码被包含在你的应用打包文件中。
|
|
|
+
|
|
|
+2. **运行时 (Runtime - `@angular/core`, Zone.js):**
|
|
|
+ * **启动应用 (`main.ts`):** 应用启动时,通常通过 `platformBrowserDynamic().bootstrapModule(AppModule)` 引导根模块 (`AppModule`)。
|
|
|
+ * **依赖注入 (DI) 容器:** Angular 创建根注入器。模块 (`@NgModule` 的 `providers`) 和组件 (`@Component` 的 `providers` / `viewProviders`) 中配置的服务提供商会注册到相应的注入器层级结构中。当组件或服务在构造函数中声明依赖时,注入器负责查找并创建(或返回已有实例)该依赖。
|
|
|
+ * **组件实例化与视图创建:**
|
|
|
+ * 当 Angular 需要渲染一个组件(比如因为路由导航到它,或者它作为另一个组件的子组件),它会:
|
|
|
+ * 使用 DI 创建该组件的实例。
|
|
|
+ * 调用组件的生命周期钩子(如 `ngOnInit`)。
|
|
|
+ * 执行该组件对应的**视图工厂函数**。这个函数:
|
|
|
+ * 创建组件的 **视图** (一个内部数据结构)。
|
|
|
+ * 根据编译时生成的**增量 DOM 指令**,创建实际的 **DOM 元素**,并将其附加到父元素上(通常是 `index.html` 中的 `<app-root>`)。
|
|
|
+ * 建立 **数据绑定** 的连接。
|
|
|
+ * **变更检测 (Change Detection):** 这是 Angular 保持视图与数据同步的核心机制。
|
|
|
+ * **Zone.js (猴子补丁):** Angular 使用 Zone.js 库。Zone.js 拦截(“猴子补丁”)了浏览器中所有常见的异步操作(`setTimeout`, `setInterval`, `addEventListener`, `Promise`, `fetch`, `XMLHttpRequest` 等)。当一个异步事件(如点击事件、HTTP 响应返回、定时器触发)发生时,Zone.js 能通知 Angular:“嘿,有事情发生了,世界可能变了!”
|
|
|
+ * **触发变更检测:** 收到 Zone.js 的通知后,Angular 会从上到下(通常是根组件开始)**检查整个组件树**。对于每个组件:
|
|
|
+ * 它查看组件类中所有用于数据绑定的属性(比如模板中 `{{ user.name }}` 对应的 `user.name`)。
|
|
|
+ * 比较这些属性的当前值是否和上次变更检测时的值**发生了变化**(默认使用 `===` 严格相等比较)。
|
|
|
+ * **更新视图:** 如果检测到变化,Angular 就会执行该组件视图对应的编译时生成的**增量 DOM 更新指令**。这些指令知道如何**高效地直接操作真实 DOM**,只更新发生变化的那一小部分。例如,如果 `users` 数组里只有一个用户的 `name` 变了,Angular 只会更新那个特定 `<li>` 里的文本节点,不会重渲整个列表。
|
|
|
+ * **优化策略:** Angular 提供 `ChangeDetectionStrategy.OnPush` 策略。使用此策略的组件,只有当它的 `@Input` 引用发生变化,或者组件内部触发了事件(或异步管道收到新值),Angular 才会检查它及其子组件,大大减少不必要的检查。
|
|
|
+
|
|
|
+**总结底层原理:**
|
|
|
+
|
|
|
+1. **编译是核心:** Angular 强大的模板编译器将你声明式的模板转换成高效的、命令式的增量 DOM 指令代码。
|
|
|
+2. **运行时驱动:** 应用启动时构建组件树和视图树,依赖注入管理服务实例。
|
|
|
+3. **变更检测同步:** 利用 Zone.js 监控异步事件,触发变更检测流程。
|
|
|
+4. **增量 DOM 更新:** 变更检测过程中,利用编译生成的指令,精准计算最小 DOM 变更并高效执行,保持视图与数据同步。
|
|
|
+5. **依赖注入连接:** 贯穿始终的 DI 系统负责创建和管理组件、服务等实例,并解决它们的依赖关系,使代码松耦合、易测试。
|
|
|
+
|
|
|
+**最终效果:** 你作为开发者,只需要用 TypeScript 定义组件的数据和逻辑,用 HTML-like 的模板语法声明视图结构和数据绑定关系。Angular 的编译器和运行时引擎会悄无声息地、高效地完成从数据变化到视图更新的所有繁重且易错的工作,让你能专注于应用的核心业务逻辑和用户体验。这就是框架的强大之处!
|