TypeScript 是 JavaScript 的静态类型检查系统,它是一门强类型语言,弥补了 JS 弱类型的缺陷。
TS是怎么把JS变成强类型的呢?通过以下这些手段:
- 扩展 JS 的数据类型。
- 类的增强,增加修饰符系统和装饰器,前者用来约束类成员的行为或访问权限,后者是一种特殊类型的声明,用于附加或修改类、类成员(方法、属性、访问器)或参数的行为。
- 提供方便类型检测的特性,如类型注解、类型推论、类型断言、类型保护、类型兼容性、类型导入等。
- 提供编译器配置选项,根据配置编译出不同的 JS 代码。
- 提供声明文件(以.d.ts结尾的文件),用于为JS代码提供类型声明。
一 数据类型
本文对TS的类型和简单使用做概括,一些简单的JS原生类型不做说明。
-
string
let name: string = "bob"; -
number
let decLiteral: number = 6; -
boolean
let isDone: boolean = false; -
bigint
const oneHundred: bigint = BigInt(100); -
symbol
const sym(: symbol) = Symbol("key"); -
null
默认情况下null和undefined是所有类型的子类型,就是说你可以把null和undefined赋值给其它类型的变量。
let n: null = null; -
undefined
let u: undefined = undefined; -
object
object表示非原始类型,也就是除number,string,boolean,symbol,bigint,null或undefined之外的类型。
let obj: object = { name: "Alice" }; -
never
表示的是那些永不存在的值的类型,never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。即使 any也不可以赋值给never。
1.存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
2.推断的返回值类型为never
function fail() {
return error("Something failed");
}
-
any
可以表示任何类型的值。
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay
-
void
表示没有任何类型,当一个函数没有返回值时,你通常会见到其返回值类型是void(js中函数没有返回值时返回undefined,但TS做了更严格的语义上的区分)。
function warnUser(): void {
console.log("This is my warning message");
}
-
unknown
类似 any,但更安全,操作前需进行类型检查。
let userInput: unknown; -
函数类型
函数类型包含两部分:参数类型和返回值类型。
JS有五种函数类型:普通函数、异步函数、生成器函数、访问器属性、静态方法。
本文只介绍普通函数在TS中的运用,其它的函数类型使用非常自然,一样的对参数和返回值约束即可。
普通函数类型有三种表达方式:function声明、function函数表达式、箭头函数表达式
函数类型的注解有两种位置写法,一种是写在变量声明处,另一种是function声明或函数表达式的内部。
let add: (a: number, b: number) => number; 【类型注解写在变量声明处】
const func: (a: number, b: number) => number = (a, b) => a + b;【类型注解写在变量声明处】
function add(a: number, b: number): number { return a + b; } 【类型注解写在function声明内部】
const add = function add(a: number, b: number): number { return a + b; } 【类型注解写在function函数表达式内部】
const add = (a: number, b: number): number => a + b; 【类型注解写在箭头函数表达式内部】
函数重载:为同一个函数提供多个类型定义叫函数重载,可以对函数调用时的参数和返回值起到约束效果。
// 函数重载声明
function reverse(input: string): string;
function reverse<T>(input: T[]): T[];
// 函数实现
function reverse(input: any): any {
if (typeof input === "string") {
return input.split("").reverse().join("");
} else if (Array.isArray(input)) {
return [...input].reverse(); // 创建新数组避免修改原数组
}
}
// 使用示例
const str = reverse("hello"); // 类型为 string
const arr = reverse([1, 2, 3]); // 类型为 number[]
console.log(str); // "olleh"
console.log(arr); // [3, 2, 1]
可选参数:在TS里我们可以在参数名旁使用?实现可选参数的功能。
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
-
数组类型
数组的两种注解方式:
let list: number[] = [1, 2, 3];let list: Array<number> = [1, 2, 3];
-
元组类型
表示固定长度和类型的数组,各元素类型可不同。
let x: [string, number] = ['abc',123]; -
字面量类型
约束变量为特定值。
type Easing = "ease-in" | "ease-out" | "ease-in-out"; -
类型别名
定义自定义类型。
type StringOrNumber = string | number; -
枚举类型
为一组值提供命名常量。
enum Color { Red=1, Green, Blue }; -
类类型
TS对类做了很多加强改造,使JS可以像Java一样非常丝滑的使用面向对象思维进行编程,一个关于TS类的例子,涉及全面的知识点:
// 1. 抽象类与继承
abstract class Vehicle {
// 2. 访问修饰符(public protected private)
private readonly VIN: string; // 3. readonly修饰符
constructor(
public brand: string, // 4. 参数属性简写
protected model: string,
private _productionYear: number
) {
this.VIN = this.generateVIN();
}
// 5. 抽象方法
abstract getVehicleInfo(): string;
// 6. 静态方法
static createDefaultVehicle() {
return new Car('Toyota', 'Camry', 2023, 4);
}
private generateVIN(): string {
return `VIN-${Math.random().toString(36).slice(2, 10)}`;
}
}
// 7. 接口实现
interface Serializable {
serialize(): string;
}
// 8. 继承抽象类
class Car extends Vehicle implements Serializable {
// 9. 静态属性
static carTypes: string[] = ['Sedan', 'SUV', 'Coupe'];
constructor(
brand: string,
model: string,
productionYear: number,
private doors: number
) {
super(brand, model, productionYear);
}
// 10. 实现抽象方法
getVehicleInfo(): string {
return `${this.brand} ${this.model} (${this['_productionYear']})`;
}
// 11. 方法重写
override getVehicleInfo(): string {
return super.getVehicleInfo() + ` with ${this.doors} doors`;
}
// 12. 存取器(getter/setter)
get productionYear(): number {
return this['_productionYear'];
}
set productionYear(year: number) {
if(year > 2000) {
this['_productionYear'] = year;
}
}
// 13. 实现接口方法
serialize(): string {
return JSON.stringify({
brand: this.brand,
model: this.model,
year: this.productionYear,
doors: this.doors
});
}
// 14. 方法重载
honk(times: number): void;
hont(timeMs: number): void;
honk(input: number | string): void {
// 实现逻辑
}
}
// 15. 装饰器,如何约束一个变量为类
function LogClass(target: new () => object) {
console.log(`Class ${target.name} created`);
}
@LogClass
class ElectricCar extends Car {
constructor(
brand: string,
model: string,
productionYear: number,
doors: number,
private batteryCapacity: number
) {
super(brand, model, productionYear, doors);
}
// 16. 方法覆盖
override getVehicleInfo(): string {
return `${super.getVehicleInfo()} Battery: ${this.batteryCapacity}kWh`;
}
}
// 使用示例
const myCar = new Car('Honda', 'Accord', 2023, 4);
console.log(myCar.getVehicleInfo()); // "Honda Accord (2023) with 4 doors"
console.log(Car.carTypes); // 访问静态属性
console.log(Vehicle.createDefaultVehicle()); // 调用静态方法
const electric = new ElectricCar('Tesla', 'Model S', 2023, 2, 100);
console.log(electric.serialize());
-
接口类型
用来约束类、对象、函数的规范,接口和类型别名很相似,最大的区别是约束类上的不同。
约束类:表达了某个类是否拥有某种能力。类可以用来实现某个接口,于是这个类就被接口约束了。
约束对象:
interface LabelledValue { label: string; }约束函数:
interface AddInterface { (a: number, b: number): number; },大括号只是一个定界符而已 -
联合类型
可以是多种类型之一。
let a:string | number; -
交叉类型
合并多个类型为一个类型。
// 定义两种类型
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: string;
department: string;
}
// 创建交叉类型
type EmployeePerson = Person & Employee;
// 使用交叉类型(必须包含所有属性)
const employee: EmployeePerson = {
name: "Alice",
age: 30,
employeeId: "E12345",
department: "IT"
};
console.log(employee.name); // Alice
console.log(employee.employeeId); // E12345
-
泛型
附属于函数、类、接口、类型别名之上的类型,相当于是一个类型变量,在定义时无法预先知道具体的类型,只有到调用时,才能确定它的类型。
在函数名、类名、接口名、类型别名后通过尖括号<>传入泛型。
使用extends关键字,可用于对泛型的取值进行约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
二 类的增强
修饰符
-
public:可以自由的访问成员。
-
private:约束成员不能在声明它的类的外部访问。
-
protected:受保护的成员,只能在自身和子类中访问。
-
readonly:约束成员为只读,这里可以是类、类型别名、接口等的成员。
-
abstract:定义抽象类、抽象成员。
-
static:JS中仅是标记静态成员的语法,在TS中是明确的类成员修饰符,用于控制成员属于类本身而非实例。
装饰器
为某些类、类的成员(属性、方法、方法参数)提供元数据信息(描述数据的数据),其本质是函数。
// 类装饰器
function ClassDecorator(constructor: Function) {
console.log(`类装饰器应用于类: ${constructor.name}`);
// 添加元数据或扩展类
}
// 属性装饰器
function PropertyDecorator(target: any, propertyKey: string) {
console.log(`属性装饰器应用于属性: ${propertyKey}`);
// 跟踪属性访问/修改
}
// 方法装饰器
function MethodDecorator(
target: any,
methodName: string,
descriptor: PropertyDescriptor
) {
console.log(`方法装饰器应用于方法: ${methodName}`);
// 可以修改方法行为(如添加日志)
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`${methodName} 被调用`);
return original.apply(this, args);
};
}
// 参数装饰器
function ParameterDecorator(
target: any,
methodName: string,
parameterIndex: number
) {
console.log(`参数装饰器应用于方法 ${methodName} 的第 ${parameterIndex + 1} 个参数`);
// 记录参数元数据
}
@ClassDecorator
class ExampleClass {
@PropertyDecorator
public sampleProperty: string = "Hello";
@MethodDecorator
public greet(@ParameterDecorator name: string): void {
console.log(`Welcome, ${name}!`);
}
}
// 测试代码
const example = new ExampleClass(); // 触发类装饰器
console.log(example.sampleProperty); // 触发属性装饰器(访问)
example.greet("TypeScript"); // 触发方法和参数装饰器
各装饰器触发顺序:
参数装饰器(当类被加载时)
方法装饰器
属性装饰器
类装饰器
输出结果:
参数装饰器应用于方法 greet 的第 1 个参数
方法装饰器应用于方法: greet
属性装饰器应用于属性: sampleProperty
类装饰器应用于类: ExampleClass
Hello
greet 被调用
Welcome, TypeScript!
三 类型特性
-
类型注解
一种轻量级的为函数或变量添加约束的方式,使用方式是用冒号+类型的方式。
-
类型推论
在有些没有明确指出类型的地方,编译器会根据值自动地确定类型。
-
类型演算
根据已知的信息,计算出新的类型,TS为我们提供了以下三种方式:
typeof:书写在类型约束的位置上,表示获取某个数据的类型,当它作用于类的时候,得到的类型是该类的构造函数。keyof:作用于类、接口、类型别名,用于获取其他类型中的所有成员名组成的联合类型。in:往往和keyof连用,限制某个索引类型的取值范围。 -
类型断言
在你清楚地知道一个实体具有比它现有类型更确切的类型时使用。
类型断言有两种形式:
1.尖括号
let someValue: any = "this is a string";let strLength: number = (<string>someValue).length;2.as语法
let someValue: any = "this is a string";let strLength: number = (someValue as string).length; -
类型保护
当对某个变量进行类型判断之后,在判断的语句块中便可以确定它的确切类型,触发类型保护的方式有:typeof、instanceof、in、自定义类型谓词等。
-
类型兼容性
类型赋值时,如何判定它们是否兼容?
对基本类型而言,要求完全匹配;对对象而言,考虑到JS的实际情况(比如JS广泛地使用匿名对象),TS提出用鸭子辩型法(子结构辩型法)进行判断,即判断某个事物是不是鸭子,只用判断它是否具有鸭子的特征即可。
-
类型导入
TS使用了两个概念扩展了import语法,用于声明类型的导入。
- import type:一个只能导入类型的导入语句。
- 内联type导入:在单个导入中添加前缀type,以指示导入的引用是一种类型。
四 编译器配置选项
TS所有编译配置选项文档链接:www.tslang.cn/docs/handbo…
五 声明文件
以.d.ts结尾的文件,用于为js代码提供类型声明,声明文件可以放在项目的这些位置:
(1)放置到tsconfig.json配置中包含(includes选项)的目录
(2)放置到node_modules/@types文件夹中
(3)根据typeRoots选项手动配置(使用这种方式,前面两种方式将失效)
(4)与JS代码所在目录相同,并且文件名也相同的文件 最佳
如果代码是TS写的,可以用declaration配置的方式自动生成声明文件,如果代码是JS写的需要手动编写声明文件。编写声明文件新用到的语法关键字有:三斜线指令、declare、namespace、module。
三斜线指令:包含单个XML标签的单行注释,注释的内容会作为编译器指令使用。
declare:为 TS 编译器提供类型声明,告诉编译器某个实体(变量、函数、类、模块等)已经存在,并在编译阶段提供其类型信息,但不生成任何实际的 JavaScript 代码。
namespace:表示命名空间,可以将其认为是一个对象,命名空间中的内容,必须通过命名空间.成员名的方式访问。
module:为第三方JS库或无类型的模块提供完整的类型定义。
总结:
JS最为一门弱类型语言,不利于编写大型应用,但是TS从根本上解决了这一问题,它提供了一套新的类型系统。
同时它也是可选的,也就是说用户可以自由的选择性地使用,并且能够按照自己的需求配置编译选项。