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 是响应式编程的核心构建块,可以方便地组合、变换和过滤数据流。

主要特性:

  1. 延迟执行: Observable 本身不会立刻执行,只有在 subscribe 被调用后,才会开始产生数据。
  2. 多播 (Multicasting) vs 单播 (Unicasting):
    • 单播: 默认情况下,每次订阅都会创建一个新的 Observable 执行,每个观察者接收的数据都是独立的。
    • 多播: 你可以使用操作符 (例如 share, publish) 将 Observable 转换为多播,多个观察者会共享同一个数据源。
  3. 错误处理: Observable 可以发射三种类型的消息:
    • next(value): 发射一个数据值。
    • error(err): 发射一个错误。
    • complete(): 表示数据流结束。
  4. 取消订阅 (Unsubscribe): 你可以取消订阅 Observable,来停止接收数据,并释放资源。
  5. 操作符 (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 的典型应用场景

  1. 事件驱动的架构
    • 用于解耦逻辑,例如模块间通信。
  2. 实时系统
    • 构建实时聊天、通知系统。
  3. 异步任务管理
    • 在异步任务完成时触发事件并通知监听者。

基础

构造函数参数

class MyClass {
  constructor(private readonly myService: MyService) {}
}

这种写法,ts会做两件事情:

  1. 声明一个与参数同名的私有只读成员变量。
  2. 将参数的值赋给改成员变量(如果适用的情况下,完成依赖注入)。

模块

每个文件都可以看作是一个模块,可以importexport,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),每个阶段都有其特定的任务队列和处理逻辑。这些阶段按照特定的顺序循环执行,构成了一次完整的迭代。以下是事件循环迭代的主要阶段,按照执行顺序排列:

  1. Timers阶段(Timers Phase)
    • 目的:处理到期的定时器(timers)回调函数,例如 setTimeout()setInterval()设置的回调。
    • 过程
      • 检查是否有到期的定时器。
      • 如果有到期的定时器,则按照到期时间顺序执行其回调函数。
      • 如果当前阶段执行时间超过了预定的时间阈值,事件循环会提前进入下一个阶段。
    • 注意定时器的回调函数不是在精确时间点执行的,而是在定时器到期后的下一个事件循环迭代的Timers阶段执行。实际执行时间会受到其他任务和事件循环自身开销的影响。
  2. Pending Callbacks阶段(Pending Callbacks Phase)
    • 目的:处理一些操作系统(OS)内核报告的错误,例如TCP错误,以及某些系统操作的回调函数(例如,某些类型的系统事件)。
    • 过程
      • 执行上一轮Poll阶段中延迟下来的I/O回调函数。
      • 主要处理一些系统级别的回调,例如网络连接错误等。
  3. 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队列。
    • 关键点:大部分I/O操作的回调函数(例如,文件读取、网络请求)都会在Poll阶段被处理。
  4. Check阶段(Check Phase)
    • 目的:执行setImmediate()设置的回调函数。
    • 过程
      • 执行setImmedate()队列中的所有回调函数。
    • setImmediate()的特点:setImmediate()设计的目的是在Poll阶段结束之后立即执行回调函数,它比setTimeout(...,0)更优先在Poll阶段之后执行。
  5. Close Callbacks阶段(Close Callbacks Phase):
    • 目的:处理"close"事件的回调函数,例如socket.on('close', ...)
    • 过程
      • 执行所有"close"事件的回调函数。
      • 通常用于清理资源和执行最后的清理操作。
  6. 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