8 | - 极客时间 重学前端
9 | - [performance](https://github.com/barretlee/performance-column)
10 | - [web前端面试 - 面试官系列](https://vue3js.cn/interview/)
11 | - https://github.com/stephentian/33-js-concepts
12 | - https://github.com/javascript-tutorial/zh.javascript.info
--------------------------------------------------------------------------------
/content/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Frontend Learning
3 | ---
4 |
5 | ## 主要内容
6 |
7 | {{< cards cols="2" >}}
8 | {{< card link="/frontend-learn/docs/guide" title="前端基础" icon="code" subtitle="HTML,CSS,Javascript,Typescript。" >}}
9 | {{< card link="/frontend-learn/docs/framework" title="前端框架" icon="color-swatch" subtitle="Vue,Angular 等。" >}}
10 | {{< card link="/frontend-learn/docs/practice" title="前端工程实践" icon="cube" subtitle="记录 Vite,Cypress 等工具的使用和原理。" >}}
11 | {{< card link="/frontend-learn/docs/node" title="Node.js 底层原理" icon="beaker" subtitle="Node.js 底层原理、浏览器的原理。" >}}
12 | {{< /cards >}}
13 |
14 | ## 互动与勘误
15 |
16 | 阅读过程中遇到任何问题 (包括但不限于语法语病、错别字、文章结构、配图等),可以直接向仓库提交 PR 或 Issues, 我会尽量在第一时间处理。
17 |
18 | 该文档仅供学习使用,如有侵权请联系我删除。感谢!😄
19 |
--------------------------------------------------------------------------------
/content/docs/_index.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/content/docs/framework/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ⚡ 前端框架
3 | weight: 2
4 | ---
5 |
--------------------------------------------------------------------------------
/content/docs/framework/angular/01_overview.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 概述
3 | weight: 1
4 | ---
5 |
6 | ## NgModule
7 |
8 | Angular 应用是由一个个模块组成的,称作 **NgModule**。
9 |
10 | NgModule 是一组相关功能的集合,专注于某个应用领域,可以将组件和一组相关代码关联起来,是应用组织代码结构的一种方式。
11 |
12 | NgModule 可以从其它 NgModule 中导入功能,并允许导出它们自己的功能供其它 NgModule 使用。
13 |
14 | NgModule 是由 NgModule 装饰器函数装饰的类。
15 |
16 | ```javascript
17 | import { BrowserModule } from '@angular/platform-browser';
18 | import { NgModule } from '@angular/core';
19 |
20 | @NgModule({
21 | imports: [
22 | BrowserModule
23 | ]
24 | })
25 | export class AppModule { }
26 | ```
27 |
28 | ### `@NgModule` 元数据
29 |
30 | NgModule 是一个带有 `@NgModule()` 装饰器的类。`@NgModule()` 元数据对象比较重要的属性:
31 |
32 | - `declarations` 属于当前 NgModule 的组件、指令、管道。
33 | - `exports` 导出当前 NgModule 的组件、指令、管道的列表。
34 | - `imports` 导入的 NgModule、组件、指令、管道的列表。
35 | - `providers` 当前 NgModule 所需的服务。
36 | - `bootstrap` 应用的主视图,称为根组件。它是应用中所有其它视图的宿主。只有 **root module** 才应该设置这个 `bootstrap` 属性。
37 |
38 | ## 组件
39 |
40 | 组件用来描述用户界面,它由三部分组成,组件类、组件模板、组件样式,它们可以被集成在组件类文件中,也可以是三个不同的文件。
41 |
42 | 组件类用来编写和组件直接相关的界面逻辑,在组件类中要关联该组件的组件模板和组件样式。
43 |
44 | 组件模板用来编写组件的 HTML 结构,通过数据绑定标记将应用中数据和 DOM 进行关联。
45 |
46 | 组件样式用来编写组件的组件的外观,组件样式可以采用 CSS、LESS、SCSS、Stylus
47 |
48 | 在 Angular 应用中至少要有一个根组件,用于应用程序的启动。
49 |
50 | 组件类是由 Component 装饰器函数装饰的类。
51 |
52 | ```javascript
53 | import { Component } from "@angular/core"
54 |
55 | @Component({
56 | selector: "app-root",
57 | templateUrl: "./app.component.html",
58 | styleUrls: ["./app.component.css"]
59 | })
60 | export class AppComponent {
61 | title = "angular-test"
62 | }
63 | ```
64 |
65 | ## 服务
66 |
67 | 服务用于放置和特定组件无关并希望跨组件共享的数据或逻辑。
68 |
69 | 服务出现的目的在于解耦组件类中的代码,是组件类中的代码干净整洁。
70 |
71 | 服务是由 `Injectable` 装饰器装饰的类。
72 |
73 | ```javascript
74 | import { Injectable } from '@angular/core';
75 |
76 | @Injectable({})
77 | export class AppService { }
78 | ```
79 |
80 | 服务的实例对象由 Angular 框架中内置的依赖注入系统创建和维护。服务是依赖需要被注入到组件中。
81 |
82 | 在组件中需要通过 `constructor` 构造函数的参数来获取服务的实例对象。
83 |
84 | 在组件中获取服务实例对象,写法如下。
85 |
86 | ```javascript
87 | import { AppService } from "./AppService"
88 |
89 | export class AppComponent {
90 | constructor (
91 | private appService: AppService
92 | ) {}
93 | }
94 | ```
95 |
96 | Angular 会根据你指定的服务的类型来传递你想要使用的服务实例对象。
97 |
98 | 在 Angular 中服务被设计为单例模式,这也正是为什么服务可以被用来在组件之间共享数据和逻辑的原因。
99 |
--------------------------------------------------------------------------------
/content/docs/framework/angular/05_directive.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 指令
3 | weight: 5
4 | ---
5 |
6 | 指令是 Angular 提供的操作 DOM 的途径。指令分为属性指令和结构指令。
7 |
8 | 属性指令:修改现有元素的外观或行为,`selector` 使用 `[]` 包裹。
9 |
10 | 结构指令:增加、删除 DOM 节点以修改布局,使用 `*` 作为指令前缀。
11 |
12 | ## 内置指令
13 |
14 | ### *ngIf
15 |
16 | 根据条件渲染 DOM 节点或移除 DOM 节点。
17 |
18 | ```html
19 | 没有更多数据
20 | ```
21 |
22 | ```html
23 | 0; then dataList else noData">
24 | 课程列表
25 | 没有更多数据
26 | ```
27 |
28 | ### [hidden]
29 |
30 | 根据条件显示 DOM 节点或隐藏 DOM 节点。
31 |
32 | ```html
33 | 课程列表
34 | 0">没有更多数据
35 | ```
36 |
37 | ### *ngFor
38 |
39 | 遍历数据生成HTML结构
40 |
41 | ```javascript
42 | interface List {
43 | id: number
44 | name: string
45 | age: number
46 | }
47 |
48 | list: List[] = [
49 | { id: 1, name: "张三", age: 20 },
50 | { id: 2, name: "李四", age: 30 }
51 | ]
52 | ```
53 |
54 | ```html
55 |
65 |
66 | ```
67 |
68 | #### trackBy
69 |
70 | 在 Angular 中遍历数组时,会用到 `ngFor` 指令,如果数组中的数据改变了(新数组替换旧数组),Angular 会删除与数据关联的所有 DOM 元素,然后再次创建它们。这意味着将有很多 DOM 操作。
71 |
72 | 使用 `*ngFor` 的 `trackBy` 属性,Angular 可以仅更改和重新渲染那些已更改的条目,而不是重新加载整个条目列表。
73 |
74 | ```typescript
75 | trackByItems(index: number, item: Item): number {
76 | return item.id; // 只需要返回一个唯一标识就好了
77 | }
78 | ```
79 |
80 | ```html
81 |
82 | ({{ item.id }}) {{ item.name }}
83 |
84 | ```
85 |
86 | 使用 `trackBy` 的好处是自定义返回跟踪结果,以比对上次的跟踪结果,如果不一样,那么就刷新变化的页面实例(减少不必要的 Dom 刷新而带来性能的提升)。
87 |
88 | ### 自定义指令
89 |
90 | 需求:为元素设置默认背景颜色,鼠标移入时的背景颜色以及移出时的背景颜色。
91 |
92 | ```html
93 | Hello Angular
94 | ```
95 |
96 | ```javascript
97 | import { AfterViewInit, Directive, ElementRef, HostListener, Input } from "@angular/core"
98 |
99 | // 接收参的数类型
100 | interface Options {
101 | bgColor?: string
102 | }
103 |
104 | @Directive({
105 | selector: "[appHover]"
106 | })
107 | export class HoverDirective implements AfterViewInit {
108 | // 接收参数
109 | @Input("appHover") appHover: Options = {}
110 | // 要操作的 DOM 节点
111 | element: HTMLElement
112 | // 获取要操作的 DOM 节点
113 | constructor(private elementRef: ElementRef) {
114 | this.element = this.elementRef.nativeElement
115 | }
116 | // 组件模板初始完成后设置元素的背景颜色
117 | ngAfterViewInit() {
118 | this.element.style.backgroundColor = this.appHover.bgColor || "skyblue"
119 | }
120 | // 为元素添加鼠标移入事件
121 | @HostListener("mouseenter") enter() {
122 | this.element.style.backgroundColor = "pink"
123 | }
124 | // 为元素添加鼠标移出事件
125 | @HostListener("mouseleave") leave() {
126 | this.element.style.backgroundColor = "skyblue"
127 | }
128 | }
129 | ```
130 |
--------------------------------------------------------------------------------
/content/docs/framework/angular/06_pipe.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 管道
3 | weight: 6
4 | ---
5 |
6 | 管道的作用是转换组件模板数据。要在 HTML 模板中指定值的转换方式,使用管道操作符 `|`。
7 |
8 | ## 内置管道
9 | - `date` 日期格式化
10 | - `currency` 货币格式化
11 | - `uppercase` 转大写
12 | - `lowercase` 转小写
13 | - `json` 格式化 json 数据
14 |
15 | ```html
16 | {{ date | date: "yyyy-MM-dd" }}
17 | {{ num | currency: "¥" }} // ¥{num}
18 | ```
19 |
20 | ## 自定义管道
21 |
22 | 需求:指定字符串不能超过规定的长度
23 |
24 | ```javascript
25 | // summary.pipe.ts
26 | import { Pipe, PipeTransform } from '@angular/core';
27 |
28 | @Pipe({
29 | name: 'summary'
30 | });
31 | export class SummaryPipe implements PipeTransform {
32 | transform (value: string, limit?: number) {
33 | if (!value) return null;
34 | let actualLimit = (limit) ? limit : 50;
35 | return value.substr(0, actualLimit) + '...';
36 | }
37 | }
38 | ```
39 |
40 | ```typescript
41 | // app.module.ts
42 | import { SummaryPipe } from './summary.pipe'
43 | @NgModule({
44 | declarations: [
45 | SummaryPipe
46 | ]
47 | });
48 | ```
49 |
50 | 使用管道:
51 |
52 | ```html
53 | {{ 'test' | summary: 100 }}
54 | ```
55 |
56 | 管道传参,使用 `:` 分隔,如果有多个参数,每个参数之间用 `:` 分隔(`| summary: 100: 200`)。
57 |
--------------------------------------------------------------------------------
/content/docs/framework/angular/08_service.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 服务
3 | weight: 8
4 | ---
5 |
6 | ## 创建服务
7 |
8 | ```javascript
9 | import { Injectable } from '@angular/core';
10 |
11 | @Injectable({
12 | providedIn: 'root'
13 | })
14 | export class TestService { }
15 | ```
16 |
17 | 使用:
18 |
19 | ```javascript
20 | export class AppComponent {
21 | // 这里的 TestService 实际上就是获取示例对象的 Token(唯一标识)
22 | constructor (private _test: TestService) {}
23 | }
24 | ```
25 |
26 | ## 服务的作用域
27 |
28 | 使用服务可以轻松实现跨模块跨组件共享数据,这取决于服务的作用域。
29 |
30 | 1. 在根注入器中注册服务,所有模块使用同一个服务实例对象。
31 |
32 | ```javascript
33 | import { Injectable } from '@angular/core';
34 |
35 | @Injectable({
36 | providedIn: 'root'
37 | })
38 |
39 | export class CarListService {
40 | }
41 | ```
42 |
43 | 2. 在模块级别注册服务,该模块中的所有组件使用同一个服务实例对象。
44 |
45 | 新语法:
46 | ```javascript
47 | import { Injectable } from '@angular/core';
48 | import { CarModule } from './car.module';
49 |
50 | @Injectable({
51 | providedIn: CarModule,
52 | })
53 |
54 | export class CarListService {
55 | }
56 | ```
57 |
58 | 老语法:
59 | ```javascript
60 | import { CarListService } from './car-list.service';
61 |
62 | @NgModule({
63 | providers: [CarListService],
64 | })
65 | export class CarModule {
66 | }
67 | ```
68 |
69 | 3. 在组件级别注册服务,该组件及其子组件使用同一个服务实例对象。
70 |
71 | ```javascript
72 | import { Component } from '@angular/core';
73 | import { CarListService } from '../car-list.service.ts'
74 |
75 | @Component({
76 | selector: 'app-car-list',
77 | templateUrl: './car-list.component.html',
78 | providers: [CarListService]
79 | })
80 | ```
81 |
82 | {{< callout type="info" >}}
83 | `providedIn: 'root'` 只是指明服务是全局单例的,这意味着整个应用中只有一个服务实例,通常用于跨多个组件和服务共享的场景。
84 | 但是,`providedIn: 'root'` 仍然可以在组件或模块的 `providers` 数组中重新提供该服务,这样它在该组件或模块中会有一个单独的实例,
85 | 其他地方仍然会使用全局单例实例。
86 | {{< /callout >}}
87 |
88 |
--------------------------------------------------------------------------------
/content/docs/framework/angular/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Angular
3 | weight: 5
4 | ---
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/content/docs/framework/rxjs/04_practice.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 实践
3 | weight: 4
4 | ---
5 |
6 | ## 优雅的资源释放
7 |
8 | 对于无限值必须要取消订阅,反之可以不需要。例如监听 DOM 元素的事件:
9 |
10 | ```typescript
11 | Observable.fromEvent(node, 'input')
12 | .subscribe(value => {
13 | console.log(value);
14 | });
15 | ```
16 |
17 | 因为如果不取消订阅,事件所关联的方法会一直被占用,导致内存泄露。
18 |
19 | ### 传统方式
20 |
21 | ```typescript
22 | @Component({
23 | selector: 'app-demo',
24 | template: `
25 | Hello, world!
26 | `
27 | })
28 | export class GeneralComponent implements OnDestroy {
29 | private readonly _destroy$ = new Subject();
30 |
31 | private timer;
32 |
33 | constructor() {
34 | this.timer = interval(1000).pipe(takeUntil(this._destroy$)).subscribe(console.log);
35 | }
36 |
37 | ngOnDestroy() {
38 | this._destroy$.next();
39 | this._destroy$.complete();
40 | }
41 | }
42 | ```
43 |
44 | 上面的在组件中定义了一个私有变量 `_destroy$`,是一个 `Subject` 对象,用于在组件销毁时发出信号以释放资源。通过 `takeUntil(this._destroy$)` 操作符来限制 `Observable` 的生命周期,在 `_destroy$` 发出信号时停止发出值。
45 |
46 | 这种方式虽然使用了 `takeUntil` 来限制 `Observable` 的生命周期,但是仍然需要在 `ngOnDestroy` 钩子中手动调用 `_destroy$.next()` 和 `_destroy$.complete()` 来确保释放资源。容易遗漏而引发错误。
47 |
48 |
49 | ### 继承方式
50 |
51 | ```typescript
52 | @Directive()
53 | export class BaseComponent implements OnDestroy {
54 | // protected, not private
55 | protected readonly _destroy$ = new Subject();
56 |
57 | ngOnDestroy() {
58 | this._destroy$.next();
59 | this._destroy$.complete();
60 | }
61 | }
62 |
63 | @Component({
64 | selector: 'app-demo',
65 | template: `
66 | Hello, world!
67 | `
68 | })
69 | export class GeneralComponent extends BaseComponent {
70 | constructor() {
71 | super();
72 |
73 | interval(1000).pipe(takeUntil(this._destroy$)).subscribe(console.log);
74 | }
75 | }
76 | ```
77 |
78 | 继承方式可以减少了在每个组件中手动管理资源释放的重复性工作。但是导致了派生组件与基类紧密耦合。一个派生类只能继承自一个基类。如果要在不同的组件中共享不同的基础逻辑,就会受到继承单一基类的限制。而且继承方式导致代码的可读性和可维护性下降。
79 |
80 | ### DestroyRef 机制
81 |
82 | ```typescript
83 | function destroyRefFactory() {
84 | const destroy$ = new Subject();
85 | const destroyRef = inject(DestroyRef);
86 |
87 | destroyRef.onDestroy(() => {
88 | destroy$.next();
89 | destroy$.complete();
90 | })
91 |
92 | return destroy$;
93 | }
94 |
95 | @Component({
96 | selector: 'app-demo',
97 | template: `
98 | Hello, world!
99 | `
100 | })
101 | export class GeneralComponent implements OnDestroy {
102 | private readonly _destroy$ = destroyRefFactory();
103 |
104 | constructor() {
105 | interval(1000).pipe(takeUntil(this._destroy$)).subscribe(console.log)
106 | }
107 | }
108 | ```
109 |
110 | 基于 `DestroyRef` 机制,不需要在组件中手动释放资源。而且不仅限于单一订阅场景,它在多个订阅场景中同样适用。
111 |
112 | ### 自定义操作符
113 |
114 | 基于 `DestroyRef` 机制的实现方式简洁灵活,但是仍然需要在组件中声明 `_destroy$`。通过自定义操作符可以将释放资源的逻辑封装在操作符内部,让代码更加整洁,使资源释放与业务逻辑解耦。
115 |
116 | ```typescript
117 | function takeUntilDestroyed(destroyRef?: DestroyRef): MonoTypeOperatorFunction {
118 | if (!destroyRef) {
119 | destroyRef = inject(DestroyRef);
120 | }
121 |
122 | const destroy$ = new Observable(observer => {
123 | return destroyRef!.onDestroy(() => {
124 | observer.next();
125 | observer.complete();
126 | });
127 | })
128 |
129 | return (source: Observable) => {
130 | return source.pipe(takeUntil(destroy$))
131 | }
132 | }
133 |
134 | @Component({
135 | selector: 'app-demo',
136 | template: `
137 | Hello, world!
138 | `
139 | })
140 | export class GeneralComponent implements OnDestroy {
141 | constructor() {
142 | interval(1000).pipe(takeUntilDestroyed()).subscribe(console.log)
143 | }
144 | }
145 | ```
146 |
147 | `@angular/core/rxjs-interop` 中已经提供了 `takeUntilDestroyed` 操作符。
148 |
149 |
--------------------------------------------------------------------------------
/content/docs/framework/rxjs/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: RxJS
3 | weight: 4
4 | ---
5 |
--------------------------------------------------------------------------------
/content/docs/framework/vue/_index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Vue
3 | weight: 6
4 | draft: true
5 | ---
6 |
7 | # Vue
8 |
--------------------------------------------------------------------------------
/content/docs/framework/vue/basic/component_pack.md:
--------------------------------------------------------------------------------
1 | # Vue 如何封装组件
2 |
3 | 封装通用组件必须具备高性能、低耦合的特性,如何封装一个好的组件?
4 |
5 | ## 数据从父组件传入
6 |
7 | 1. 为了低耦合,子组件就应该是无状态的。即使本身会生成数据,也只能在内部使用,不能传递出去。
8 | 2. 应该对`props`传递的参数应该添加一些验证规则
9 | 3. `props`传入的参数,不建议修改,如果必须要修改,建议定义子组件的`data`的一个属性来接收`prop`传入的值。
10 | 如果`prop`只以一种原始的值传入且需要进行转换,可以使用计算属性,比如把`prop`传入的值转成小写`return this.size.trim().toLowerCase()`。
11 |
12 | ## 在父组件处理事件
13 |
14 | 1. 事件的处理方法应当尽量放到父组件中,通用组件本身只作为一个中转,降低耦合性,保证了通用组件中的数据不被污染
15 | 2. 组件内部的一些交互行为,或者处理的数据只在组件内部传递,这时候就不需要用`$emit`了
16 |
17 | ## 留一个`slot`
18 |
19 | 1. 一个通用组件,往往不能够完美的适应所有应用场景,留一个`slot`来让父组件实现一些功能。
20 | 2. 开发通用组件的时候,只要不是独立性很高的组件,建议都留一个`slot`,即使还没想好用来干什么。
21 |
22 | ## 不要依赖 Vuex
23 |
24 | 1. Vuex 用来做组件状态管理的,类似一个全局变量,会一直占用内存,除非刷新页面,不建议用来非父子组件通信。
25 | 2. Vuex 在写入数据庞大的 state 的时候,就会产生内存泄露
26 | 3. 如果刷新页面时需要保留数据,可以通过 web storage 保存。
27 |
28 | ## 合理运用 scoped
29 | 在编写组件的时候,可以在`
34 |
35 |
36 |
37 |
38 |
39 |
40 |
51 |
52 |