typescript基础
核心库
async_hooks
import * as asyncHooks from 'async_hooks';async_hooks是一个Node.js的核心模块,主要用于跟踪异步操作的生命周期和上下文信息,简单来说,async_hooks允许你深入了解Node.js事件循环内部的异步操作,并提供钩子函数(hooks)在异步操作的不同阶段被调用。
import { AsyncLocalStorage } from 'async_hooks';
const store = new AsyncLocalStorage<{ id: number }>();
async function asyncOperation() {
console.log('before run:', store.getStore());
await store.run({ id: 123 }, async () => {
console.log('inside run:', store.getStore());
await new Promise(resolve => setTimeout(resolve, 100)); // 模拟异步操作
console.log('inside run after await:', store.getStore());
});
console.log('after run:', store.getStore());
}
asyncOperation();
// 输出为:
// before run: undefined
// inside run: { id: 123 }
// inside run after await: { id: 123 }
// after run: undefined
这个方式的调用:await store.run({ id: 123 }, async () => {...}会使得run内部的的函数调用通过store.getStore()方法获取的数据都是{ id: 123 },不论嵌套多少层,这样异步传参就方便很多了。
Observable
RxJS (Reactive Extensions for JavaScript): 最流行的响应式编程库,提供了丰富的 Observable 实现和操作符。
Observable是一个表示数据流的类型,它来自于响应式编程 (Reactive Programming) 的概念。你可以把它想象成一个随着时间推移发射一系列值的“水龙头”。 核心概念:
- 数据流: Observable 代表一个数据序列,这个数据序列可以是同步的,也可以是异步的。
- 发射 (Emitting): Observable 可以随时发射 (emit) 数据,就像水龙头滴水一样。
- 观察者 (Observer): 需要有观察者来接收 Observable 发射的数据。观察者通过订阅 (subscribe) Observable 来接收数据。
- 异步处理: Observable 非常适合处理异步数据,例如网络请求、用户事件等。
- 响应式编程: Observable 是响应式编程的核心构建块,可以方便地组合、变换和过滤数据流。
主要特性:
- 延迟执行: Observable 本身不会立刻执行,只有在 subscribe 被调用后,才会开始产生数据。
- 多播 (Multicasting) vs 单播 (Unicasting):
- 单播: 默认情况下,每次订阅都会创建一个新的 Observable 执行,每个观察者接收的数据都是独立的。
- 多播: 你可以使用操作符 (例如 share, publish) 将 Observable 转换为多播,多个观察者会共享同一个数据源。
- 错误处理: Observable 可以发射三种类型的消息:
- next(value): 发射一个数据值。
- error(err): 发射一个错误。
- complete(): 表示数据流结束。
- 取消订阅 (Unsubscribe): 你可以取消订阅 Observable,来停止接收数据,并释放资源。
- 操作符 (Operators): Observable 可以与各种操作符一起使用,例如 map, filter, flatMap, debounce, throttle, 等等。操作符可以用来转换、过滤和组合数据流。
EventEmitter
import { EventEmitter } from 'events';在typescript中,EventEmitter通常是指来自nodejs的核心模块events的类,用于实现事件驱动编程。它可以在typescript中直接使用,并支持强类型定义,使事件的发布和订阅更加安全和易于维护。
基本用法
EventEmitter 提供了以下主要方法:
on(event: string, listener: (...args: any[]) => void): this
用于监听事件。emit(event: string, ...args: any[]): boolean
用于触发事件。once(event: string, listener: (...args: any[]) => void): this
监听事件一次,触发后自动移除。
import { EventEmitter } from 'events';
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
// 订阅事件
myEmitter.on('event', (data) => {
console.log('Event received:', data);
});
// 发布事件
myEmitter.emit('event', { message: 'Hello, EventEmitter!' });
// 输出
// Event received: { message: 'Hello, EventEmitter!' }
强类型支持
为了更好地使用 TypeScript 的类型检查,可以为事件定义类型。
import { EventEmitter } from 'events';
// 定义事件类型
interface MyEvents {
greeting: (message: string) => void;
error: (error: Error) => void;
}
// 自定义泛型 EventEmitter
class TypedEventEmitter<Events> extends EventEmitter {
on<K extends keyof Events>(event: K, listener: Events[K]): this {
return super.on(event as string, listener as (...args: any[]) => void);
}
emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): boolean {
return super.emit(event as string, ...args);
}
}
const myEmitter = new TypedEventEmitter<MyEvents>();
// 订阅事件
myEmitter.on('greeting', (message) => {
console.log('Greeting:', message);
});
myEmitter.on('error', (error) => {
console.error('Error occurred:', error.message);
});
// 发布事件
myEmitter.emit('greeting', 'Hello, TypeScript!');
myEmitter.emit('error', new Error('Something went wrong'));
// 输出
// Greeting: Hello, TypeScript!
// Error occurred: Something went wrong
优势:
- 提供了事件名称和回调函数参数的强类型检查。
- 避免了拼写错误或参数不匹配的问题。
一次性事件监听
使用 once 方法可以监听某个事件一次,触发后自动移除监听器。
myEmitter.once('greeting', (message) => {
console.log('This will only be logged once:', message);
});
myEmitter.emit('greeting', 'First call'); // 输出: This will only be logged once: First call
myEmitter.emit('greeting', 'Second call'); // 无输出
移除事件监听器
off(event: string, listener: (...args: any[]) => void): this
移除特定的监听器。removeAllListeners(event?: string): this
移除所有监听器。
const listener = (data: string) => {
console.log('Listener called with:', data);
};
myEmitter.on('event', listener);
// 移除特定监听器
myEmitter.off('event', listener);
// 发布事件后无输出,因为监听器已被移除
myEmitter.emit('event', 'Test data');EventEmitter 的典型应用场景
- 事件驱动的架构
- 用于解耦逻辑,例如模块间通信。
- 实时系统
- 构建实时聊天、通知系统。
- 异步任务管理
- 在异步任务完成时触发事件并通知监听者。
基础
类
构造函数参数
class MyClass {
constructor(private readonly myService: MyService) {}
}这种写法,ts会做两件事情:
- 声明一个与参数同名的私有只读成员变量。
- 将参数的值赋给改成员变量(如果适用的情况下,完成依赖注入)。
模块
每个文件都可以看作是一个模块,可以import、export,typescript中import获得的是对一个对象的引用。 TypeScript的模块机制:
- 模块化:TypeScript实用模块化的方式组织代码,每个文件可以被认为是一个独立的模块。
- 导入和导出:导入导出的模块都是对模块的引用,而不是像c++的include一样。
泛型
泛型 是 TypeScript 中的一种特性,它允许你在定义函数、接口或类时,不预先指定具体的类型,而是在使用时再指定具体的类型。这使得代码可以更通用、灵活,同时又能在使用时提供类型安全。
为什么需要泛型? 3. 增强代码的通用性: - 泛型可以让一个函数或类适用于多种类型,而无需为每种类型分别定义函数或类。 4. 保证类型安全: - 泛型在使用时指定类型,可以避免类型不匹配的错误。 5. 提高代码的可读性和可维护性: - 泛型使得代码更具通用性,减少重复代码。
基本语法
function identity<T>(value: T): T {
return value;
}<T>是泛型参数,表示类型参数,可以理解为一个占位符。T的具体类型在调用时才确定。 使用泛型函数:
const num = identity<number>(42); // T 被推断为 number
const str = identity<string>('Hello'); // T 被推断为 string
console.log(num); // 42
console.log(str); // Hello
泛型的应用场景
泛型函数
通用函数可以适用于任意类型。
function merge<T, U>(a: T, b: U): [T, U] {
return [a, b];
}
const result = merge<number, string>(42, 'Hello');
console.log(result); // [42, 'Hello']
泛型接口
接口可以使用泛型来定义结构。
interface KeyValue<K, V> {
key: K;
value: V;
}
const item: KeyValue<number, string> = { key: 1, value: 'Item 1' };
console.log(item); // { key: 1, value: 'Item 1' }
泛型类
类可以使用泛型来定义成员的类型。
class GenericBox<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const stringBox = new GenericBox<string>('Hello');
console.log(stringBox.getValue()); // Hello
泛型约束
使用泛型时,可以对泛型参数设置约束,限制它必须符合某些条件。
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(value: T): T {
console.log(value.length); // 确保传入的类型有 length 属性
return value;
}
logLength('Hello'); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10, name: 'Object' }); // 10
T extends Lengthwise:表示泛型参数T必须有length属性。
泛型默认值
可以为泛型参数指定默认类型。
function identityWithDefault<T = string>(value: T): T {
return value;
}
const result = identityWithDefault(); // 默认为 string
console.log(result); // undefined
事件循环迭代
在Node.js中,事件循环迭代(Event Loop Iteration) 指的是事件循环的 单个完整循环周期。事件循环是Node.js实现的非阻塞I/O和并发的核心机制,Node.js是单线程的,但是它通过事件的循环机制来实现并发。事件循环不断地轮询并处理事件队列中的事件,从而使得Node.js能够在非阻塞的情况下处理I/O操作,并响应各种事件。
事件循环的阶段(Phases)
一个完整的事件循环迭代可以被划分为几个阶段(phases),每个阶段都有其特定的任务队列和处理逻辑。这些阶段按照特定的顺序循环执行,构成了一次完整的迭代。以下是事件循环迭代的主要阶段,按照执行顺序排列:
- Timers阶段(Timers Phase):
- 目的:处理到期的定时器(timers)回调函数,例如
setTimeout()和setInterval()设置的回调。 - 过程:
- 检查是否有到期的定时器。
- 如果有到期的定时器,则按照到期时间顺序执行其回调函数。
- 如果当前阶段执行时间超过了预定的时间阈值,事件循环会提前进入下一个阶段。
- 注意:定时器的回调函数不是在精确时间点执行的,而是在定时器到期后的下一个事件循环迭代的Timers阶段执行。实际执行时间会受到其他任务和事件循环自身开销的影响。
- 目的:处理到期的定时器(timers)回调函数,例如
- Pending Callbacks阶段(Pending Callbacks Phase):
- 目的:处理一些操作系统(OS)内核报告的错误,例如TCP错误,以及某些系统操作的回调函数(例如,某些类型的系统事件)。
- 过程:
- 执行上一轮Poll阶段中延迟下来的I/O回调函数。
- 主要处理一些系统级别的回调,例如网络连接错误等。
- Poll阶段(Poll Phase):这是事件循环中最核心和重要的阶段
- 目的:处理新的I/O事件,并执行与这些事件相关的回调函数。
- 过程:
- 检查Poll队列:首先检查Poll队列中是否有待处理的I/O事件回调。
- 如果Poll队列不为空,则按照队列顺序同步执行队列中的回调函数,直到队列为空或达到阶段执行时间阈值。
- 如果Poll队列为空,则进入以下两种情况:
- 立即检查Check阶段:如果
setImmediate()回调等待执行,事件循环会结束Poll阶段,进入Check阶段。 - 等待新的I/O事件:如果没有
setImmediate()回调,Poll阶段会阻塞(poll and wait),等待新的I/O事件到达。当新的I/O事件到达时,Poll阶段会立即处理该事件,并将对应的回调函数加入Poll队列。
- 立即检查Check阶段:如果
- 检查Poll队列:首先检查Poll队列中是否有待处理的I/O事件回调。
- 关键点:大部分I/O操作的回调函数(例如,文件读取、网络请求)都会在Poll阶段被处理。
- Check阶段(Check Phase):
- 目的:执行
setImmediate()设置的回调函数。 - 过程:
- 执行
setImmedate()队列中的所有回调函数。
- 执行
setImmediate()的特点:setImmediate()设计的目的是在Poll阶段结束之后立即执行回调函数,它比setTimeout(...,0)更优先在Poll阶段之后执行。
- 目的:执行
Close Callbacks阶段(Close Callbacks Phase):- 目的:处理"close"事件的回调函数,例如
socket.on('close', ...)。 - 过程:
- 执行所有"close"事件的回调函数。
- 通常用于清理资源和执行最后的清理操作。
- 目的:处理"close"事件的回调函数,例如
- Microtasks阶段(Microtasks Phase - 不是官方阶段名称,但是概念重要):
- 目的:处理微服务队列中的任务。
- 过程:
- 在事件循环的每个阶段之间 以及 某些阶段内部(例如,在Poll阶段处理完一个 I/O事件回调后),事件循环会检查并执行 微任务队列 中的任务。
- 微任务队列主要包含:
process.nextTick()回调: 优先级最高,会在当前操作完成后立即执行,但在事件循环的下一个阶段开始之前。- Promise 的 resolved/rejected 回调: 在 Promise 状态变为 resolved 或 rejected 后,其回调函数会被加入微任务队列。
- 重要性: 微任务具有比普通回调更高的优先级,它们会在事件循环的各个阶段之间尽快执行,以确保异步操作的及时完成。 关键要点总结:
- 迭代性: 事件循环不断重复这些阶段,构成一次次的迭代。
- 阶段顺序: 阶段的执行顺序是固定的:Timers -> Pending Callbacks -> Poll -> Check -> Close Callbacks。
- Poll 阶段的核心作用: Poll 阶段是处理 I/O 事件的关键,大部分异步操作的回调都在这里处理。
setImmediate()和setTimeout()的区别:setImmediate()的回调会在 Check 阶段执行,而setTimeout(..., 0)的回调会在下一个事件循环迭代的 Timers 阶段执行,setImmediate()通常优先于setTimeout(..., 0)执行。- 微任务的优先级: 微任务 (例如
process.nextTick(), Promise 回调) 比普通回调具有更高的优先级,会在事件循环的各个阶段之间尽快执行。 - 阻塞事件循环: 如果某个阶段的任务执行时间过长 (例如,同步的 CPU 密集型操作),会阻塞事件循环,导致 Node.js 应用的响应变慢。
常用
命令行运行typescript
# 安装
npm install -g ts-node typescript
# 运行脚本
ts-node example.ts项目初始化
mkdir -p node-project
cd node-project
# 初始化package.json
npm init -y
# 安装typescript编译器,和nodejs类型定义
npm install typescript @types/node --save-dev
# 创建typescript配置文件
npx tsc --init