导图社区 TypeScript
梳理了typescript全部知识点,TypeScript是一个很好的工具,即使您没有使用过它,也可以将其纳入你的学习计划中。从点滴开始学起,慢慢的你就会很快上手的,因为TypeScript的学习曲线对初学者非常友好。祝这张导图能够帮助你
提示: 本内容由社区用户上传并分享。平台不对内容的真实性、合法性、知识产权归属及是否侵害第三方权利进行事前审核或保证。本内容可能包含受版权保护的图片、字体或其他第三方素材,使用前请自行确认授权范围。
TypeScript
概述
定义
是一种由微软开发的自由和开源的编程语言
是JavaScript的一个超集
本质上向js添加了可选的静态类型和基于类的面向对象编程
添加静态类型的原因
可以避免经典的错误 'undefined' is not a function
在不严重破坏代码的情况下,重构代码更容易
使大型、复杂的应用程序源码更易阅读
特点
强类型
增强代码的可读性和可维护性
类型系统就是最好的文档
包容性
js文件可以直接改成 ts 文件
原因:类型推论和类型兼容
即使 TypeScript 编译报错,也可以生成 JavaScript 文件
学习成本高
接口(Interfaces)
泛型(Generics)
枚举类型(Enums)
增加开发成本,减少维护成本
优势
更可靠
TypeScript代码更可靠、更容易重构。这使开发人员可以更轻松地避免错误并进行重写
更清晰
显式类型使我们代码可读性更高
更全面
是JavaScript的一个超集
基础类型
常用类型
布尔
boolean
let isDone: boolean = false;
数字
number
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
字符串
string
let name: string = "bob";
数组
元素数组
let list: number[] = [1, 2, 3];
数组泛型
let list: Array<number> = [1, 2, 3];
元组
Tuple
let x: [string, number]; x = ['hello', 10];
枚举
enum
enum Color {Red = 1, Green, Blue}; let colorName: string = Color[2]; console.log(colorName);
特殊类型
Any
直接通过编译阶段的检查
let notSure: any = 4; notSure = "aaaa"; notSure = false;
Void
表示没有任何类型,通常用于没有返回值的函数
Null
Undefined
Never
表示的是那些永不存在的值的类型
所有类型的子类型,可以赋值给任何类型
Object
表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型
类型断言
强制类型转换,不进行特殊的数据检查和解构
let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;
高级类型
交叉类型
将多个类型合并为一个类型
把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
声明
使用 & 分割多个类型
使用场景
混入
function extend<T, U>(first: T, second: U): T & U { let result = <T & U>{}; for (let id in first) { (<any>result)[id] = (<any>first)[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();
其它不适合典型面向对象模型的地方
联合类型
表示一个值可以是几种类型之一
声明
使用 | 连接多个类型
使用场景
值可以为不同类型的情况
类型保护(类型区分)
使用类型断言
let pet = getSmallPet(); if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else { (<Bird>pet).fly(); }
自定义的类型保护
表达式
parameterName is Type
function isFish(pet: Fish | Bird): pet is Fish { return (<Fish>pet).swim !== undefined; } if (isFish(pet)) { pet.swim(); } else { pet.fly(); }
strictNullChecks
不会自动地包含 null或 undefined
可以使用联合类型明确的包含它们
typeof类型保护
typeof v === "typename"
typeof v !== "typename"
"typename"必须是 "number", "string", "boolean"或 "symbol"
instanceof类型保护
通过构造函数来细化类型的一种方式
interface Padder { getPaddingString(): string } class SpaceRepeatingPadder implements Padder { constructor(private numSpaces: number) { } getPaddingString() { return Array(this.numSpaces + 1).join(" "); } } class StringPadder implements Padder { constructor(private value: string) { } getPaddingString() { return this.value; } } function getRandomPadder() { return Math.random() < 0.5 ? new SpaceRepeatingPadder(4) : new StringPadder(" "); } // 类型为SpaceRepeatingPadder | StringPadder let padder: Padder = getRandomPadder(); if (padder instanceof SpaceRepeatingPadder) { padder; // 类型细化为'SpaceRepeatingPadder' } if (padder instanceof StringPadder) { padder; // 类型细化为'StringPadder' }
右侧要求是一个构造函数
此构造函数的 prototype属性的类型,如果它的类型不为 any的话
构造签名所返回的类型的联合
类型别名
类型别名会给一个类型起个新名字。可以作用于原始值,联合类型,元组以及其它任何需要手写的类型
type Name = string; type NameResolver = () => string; type NameOrResolver = Name | NameResolver; function getName(n: NameOrResolver): Name { if (typeof n === 'string') { return n; } else { return n(); } }
声明
使用type关键字
type strType=string|number|boolean; // strType 可以表示 3 中类型 var str:strType="10"; //表示其中的一种 为字符串 str=true;
原始类型别名
通常没什么用
接口别名
// 定义一个接口 为字符串类型的 interface num1 { name: string; } interface num2 { age: number; } // 类型别名 numType 可以去描述字符串或者是数字 type numType = num1 | num2; // 描述的是 字符串 var obj1: numType = { name: "张三" }; var obj2: numType = { age: 12 }; var obj3: numType = { name: "张三", age: 12 };
和接口的区别
接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字
类型别名不能被 extends和 implements
无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名
尽量去使用接口代替类型别名
字符串字面量类型
允许你指定字符串必须的固定值
声明
type paramName = 'type1' | 'type2' ...
type Easing = "ease-in" | "ease-out" | "ease-in-out";
还可以用于区分函数重载
function createElement(tagName: "img"): HTMLImageElement; function createElement(tagName: "input"): HTMLInputElement; // ... more overloads ... function createElement(tagName: string): Element { // ... code goes here ... }
数字字面量类型
类似字符串字面量类型
枚举成员类型
可辨识联合
可以通过合并单例类型,联合类型,类型保护和类型别名来创建
也称做 标签联合或 代数数据类型
特点
具有普通的单例类型属性— 可辨识的特征(有一个共同属性)
一个类型别名包含了那些类型的联合— 联合
此属性上的类型保护
示例
interface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; } 首先我们声明了将要联合的接口。 每个接口都有 kind属性但有不同的字符串字面量类型。 kind属性称做 可辨识的特征或 标签。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起: type Shape = Square | Rectangle | Circle; 现在我们使用可辨识联合: function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; } }
完整性检查
场景
当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们
type Shape = Square | Rectangle | Circle | Triangle; function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; } // should error here - we didn't handle case "triangle" }
方式
--strictNullChecks
使用 never类型
function assertNever(x: never): never { throw new Error("Unexpected object: " + x); } function area(s: Shape) { switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.height * s.width; case "circle": return Math.PI * s.radius ** 2; default: return assertNever(s); // error here if there are missing cases } }
多态的 this类型
表示的是某个包含类或接口的子类型
在继承之后保持接口的连贯性
索引类型
keyof T
索引类型查询操作符
对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合
映射类型
Symbols
声明
通过Symbol构造函数创建的
示例
let firstName = Symbol(); let person = {}; person[firstName] = "huochai"; console.log(person[firstName]); // "huochai"
Symbol函数前不能使用new命令
Symbol函数接受一个可选参数,这段描述不可用于属性访问,但是建议添加,以便于阅读代码和调试Symbol程序
let firstName = Symbol("first name"); let person = {}; person[firstName] = "huochai"; console.log("first name" in person); // false console.log(person[firstName]); // "huochai" console.log(firstName); // "Symbol(first name)" Symbol的描述被存储在内部[[Description]]属性中,只有当调用Symbol的toString()方法时才可以读取这个属性。在执行console.log()时隐式调用了firstName的toString()方法,所以它的描述会被打印到日志中,但不能直接在代码里访问[[Description]]
特点
不可改变且唯一
let sym2 = Symbol("key"); let sym3 = Symbol("key"); sym2 === sym3; // false, symbols是唯一的
和es6用法基本一致
作用场景
可以被用做对象属性的键
防止某一个键被不小心改写或覆盖
唯一值,确保不会与其他属性名产生冲突
所有使用可计算属性名的地方,都可以使用Symbol
类型转换
仅能与布尔值互相转换
属性检索
不会出现在for...in、for...of循环中
不会被常规object遍历api返回
Object.getOwnpropertySymbols()
let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; let symbols = Object.getOwnPropertySymbols(object); console.log(symbols.length); // 1 console.log(symbols[0]); // "Symbol(uid)" console.log(object[symbols[0]]); // "12345"
内置symbols
Symbol.hasInstance
方法,会被instanceof运算符调用。构造器对象用来识别一个对象是否是其实例
Symbol.isConcatSpreadable
布尔值,表示当在一个对象上调用Array.prototype.concat时,这个对象的数组元素是否可展开
Symbol.iterator
方法,被for-of语句调用。返回对象的默认迭代器
Symbol.match
方法,被String.prototype.match调用。正则表达式用来匹配字符串
Symbol.replace
方法,被String.prototype.replace调用。正则表达式用来替换字符串中匹配的子串
Symbol.search
方法,被String.prototype.search调用。正则表达式返回被匹配部分在字符串中的索引
Symbol.species
函数值,为一个构造函数。用来创建派生对象
Symbol.split
方法,被String.prototype.split调用。正则表达式来用分割字符串
Symbol.toPrimitive
方法,被ToPrimitive抽象操作调用。把对象转换为相应的原始值
Symbol.toStringTag
方法,被内置方法Object.prototype.toString调用。返回创建对象时默认的字符串描述
Symbol.unscopables
对象,它自己拥有的属性会被with作用域排除在外
迭代器和生成器
可迭代性
当一个对象实现了Symbol.iterator属性时,我们认为它是可迭代的
内置的类型如 Array,Map,Set,String,Int32Array,Uint32Array等都已经实现了各自的Symbol.iterator
对象上的 Symbol.iterator函数负责返回供迭代的值
for..of
let list = [4, 5, 6]; for (let i of list) { console.log(i); // "4", "5", "6" }
for..of会遍历可迭代的对象,调用对象上的Symbol.iterator方法
let someArray = [1, "string", false]; for (let entry of someArray) { console.log(entry); // 1, "string", false }
遍历的value
for..of关注于可迭代对象的值
let pets = new Set(["Cat", "Dog", "Hamster"]); pets["species"] = "mammals"; for (let pet in pets) { console.log(pet); // "species" } for (let pet of pets) { console.log(pet); // "Cat", "Dog", "Hamster" }
for..in
let list = [4, 5, 6]; for (let i in list) { console.log(i); // "0", "1", "2", }
遍历的key
for..in可以操作任何对象;它提供了查看对象属性的一种方法
接口
作用
为类型命名或代码定义契约(类型检查)
描述JavaScript中对象拥有的各种各样的外形
初步使用
interface LabelledValue { label: string; } //声明接口 function printLabel(labelledObj: LabelledValue) {......} //使用接口 let myObj = {size: 10, label: "aaa"}; //入参被接口所约束 printLabel(myObj); //参数合法,编译通过
属性
可选属性
声明
在可选属性名字定义的后面加一个?符号
作用
捕获引用不存在的属性时的错误,也可对可能存在的属性进行预定义
示例
interface SquareConfig { color?: string } function getColor(config: SquareConfig):string { return config.clo; //编译报错 }
只读属性
声明
在属性名前用 readonly来指定只读属性
作用
确保对象属性在声明之后不可修改
示例
interface Point {readonly x: number;} let p1: Point = { x: 10 }; p1.x = 5; // 编译错误
与const区别
变量用 const,属性则用readonly
ReadonlyArray<T>
只读数组,仅能用类型断言重写
额外属性检查
声明
[propName: type]: [type];
作用
描述对象中可带有任意数量的其它属性
示例
interface SquareConfig { id: number; [propName: string]: any; }
类型
可索引类型
声明
[index: number]: string;
作用
描述能够“通过索引得到”的类型
示例
interface idDictionary { [id: number]: string; 0: string } let a:idDictionary = { 0: 'all', 1: 'apple' }
只读索引
readonly [index: number]: string;
函数类型
作用
描述函数类型
声明
([参数一]: type, [参数二]: type): [返回类型]
示例
interface subName { (userName: string, userAge:number): string; } let outName: subName = function(name: string, age: number) { return name+age; }
备注
函数的参数名不需要与接口里定义的名字相匹配
函数的参数也可以不指定类型,会自动根据接口推断
类类型
作用
强制一个类去符合某种契约
声明
使用implements继承
示例
// 1.实例部分接口,用来约束最终创建的实例 interface StudentInterface { id: string age: number go():void; } // 2.静态部分接口,用来约束构造器声明,返回类型为实例部分接口类型 interface StudentInfoType { new (classId: string, code: string, age: number): StudentInterface; } // 3.声明用于构造对象的方法(区别于名词‘构造函数’)调用静态部分接口进行实例化对象操作 // 通过调用静态部分接口,来进行接口约束 function createStudent(studentInfo: StudentInfoType, classId: string, code:string, age: number): StudentInterface { return new studentInfo(classId, code, age); } // 4.声明class,声明时实现 实例部分接口 class StudentItem implements StudentInterface { id: string; age: number; // constructor构造器,内部声明在createStudent中被约束 constructor(classId: string, code: string, age: number){ this.id = classId + "" + code this.age = age } go(){ console.log('gogogo') } } // 5.传入构造函数来实例化对象,在createStudent中将后续参数通过constructor构造器挂载在实例上 let 小明 = createStudent(StudentItem, '05', '33', 12) console.log(小明)
实例部分和静态部分
https://blog.csdn.net/qq_37027534/article/details/123550464
继承接口
作用
可以更灵活地将接口分割到可重用的模块里
声明
使用extends关键字
一个接口可以继承多个接口
interface Square extends Shape, PenStroke{ }
混合类型
作用
希望一个对象可以同时具有多种类型
示例
interface Counter { (start: number): string; interval: number; reset(): void; } function getCounter(): Counter { let counter = <Counter>function (start: number) { }; counter.interval = 123; counter.reset = function () { }; return counter; }
类
声明
使用class
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } } let greeter = new Greeter("world");
继承
使用extends继承
使用super()继承构造
class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34);
在构造函数里访问 this的属性之前,要先调用 super()
修饰符
public
private
私有,外界不可访问
protected
不可被外界访问,可被继承者访问
readonly
只读属性必须在声明时或构造函数里被初始化
class Octopus { readonly name: string; readonly numberOfLegs: number = 8; constructor (theName: string) { this.name = theName; } } let dad = new Octopus("Man with the 8 strong legs"); dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的
static
存在于类本身上面而不是类的实例上
意味着这个变量类型不能修改
存取器
设置变量为私有
使用get和set
let passcode = "secret passcode"; class Employee { private _fullName: string; get fullName(): string { return this._fullName; } set fullName(newName: string) { if (passcode && passcode == "secret passcode") { this._fullName = newName; } else { console.log("Error: Unauthorized update of employee!"); } } } let employee = new Employee(); employee.fullName = "Bob Smith"; if (employee.fullName) { alert(employee.fullName); }
抽象类
作用
做为其它派生类的基类使用
特点
不会直接被实例化
可以包含成员的实现细节
abstract修饰的方法不能有具体实现
声明
1.使用 abstract 关键字创建,并用abstract定义内部抽象方法
abstract class Animal { abstract makeSound(): void; move(): void { console.log('roaming the earch...'); } }
2.使用extends继承抽象类成为派生类,并实现抽象类中的抽象方法
abstract class Department { constructor(public name: string) { } printName(): void { console.log('Department name: ' + this.name); } abstract printMeeting(): void; // 必须在派生类中实现 } class AccountingDepartment extends Department { constructor() { super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super() } printMeeting(): void { console.log('The Accounting Department meets each Monday at 10am.'); } generateReports(): void { console.log('Generating accounting reports...'); } } let department: Department; // 允许创建一个对抽象类型的引用 department = new Department(); // 错误: 不能创建一个抽象类的实例 department = new AccountingDepartment(); // 允许对一个抽象子类进行实例化和赋值 department.printName(); department.printMeeting(); department.generateReports(); // 错误: 方法在声明的抽象类中不存在
泛型
释义
声明
使用尖括号<>
示例
function ids<T, U>(arg1: T, arg2: U): [T, U] { return [arg1, arg2]; }
可以适用于多个类型。 不同于使用 any,它不会丢失信息
是一种抽象的类型,我们不能直接定义一个变量类型为泛型
应用
应用对象
函数,接口或者类
应用场景
需要作用到很多类型的时候
需要被用到很多地方的时候
详解
https://blog.csdn.net/azl397985856/article/details/106913210/
常见泛型用法
泛型函数
声明
示例
function identity<T>(arg: T): T { return arg; }
使用
完整写法
使用时传入类型
let output = identity<string>("myString");
类型推论
根据初始化的值自动推断类型
let output = identity("myString");
泛型类
声明
示例
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; }
使用
实例化时传入属性
let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
注意点
泛型类指的是实例部分的类型
类的静态属性不能使用泛型类型
泛型接口
声明
示例
interface Config<T>{ (value:T):T; }
使用
示例
function getData<T>(value:T):T{ return value; } var myGetData:Config<string> = getData; myGetData('10');
泛型约束
使用某个类型才具有的api如 .size()
使用extends
interface Sizeable { size: number; } function trace<T extends Sizeable>(arg: T): T { console.log(arg.size); return arg; }
集合类泛型
Array<String>
减少代码量
类型推导
类型推导是仅仅在初始化的时候进行推导
let a = "lucifer"; // 我们没有给 a 声明类型, a 被推导为string a.toFixed(); // Property 'toFixed' does not exist on type 'string'. a.includes("1"); // ok a = 1; a.toFixed(); // 依然报错, a 不会被推导 为 number
默认参数
函数
函数类型
定义
具名函数
匿名函数
参数类型
参数列表的形式写出参数类型
返回值类型
在函数和返回值类型之前使用( =>)符号
示例
let myAdd: (baseValue: number, increment: number) => number = function(x: number, y: number): number { return x + y; };
推断类型
TypeScript会根据赋值语句的一侧已指定的类型来自动识别出另一边的类型, 叫做“按上下文归类”,是类型推论的一种
可选参数
声明
参数名旁使用 ?
function buildName(firstName: string, lastName?: string){}
可选参数必须跟在必须参数后面
默认参数
声明
参数名后面接=[默认值]
function buildName(firstName: string, lastName = "Smith"){}
可以放在必须参数的后面
可以明确的传入 undefined值来获得默认值
剩余参数
声明
在参数前添加省略号( ...)
function buildName(firstName: string, ...restOfName: string[])
方法重载
为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用
重载列表没有方法体
function fn(param1: number, param2: string):string function fn(param1:String,param2:number):string
方法实现有方法体
function fn(param1: any, param2: any):string { return param1 + param1 }
示例
function fn(param1: number, param2: string):string function fn(param1:String,param2:number):string function fn(param1: any, param2: any):string { return param1 + param1 } fn('a', 2);
枚举
作用
清晰地表达意图
创建一组有区别的用例
声明
使用enum声明
enum Direction { Up = 1, Down, Left, Right }
特点
在运行时是对象
声明时未知,则运行时计算
声明时已知,则删除声明,值内联
加const则必定内联
以上规则随typescript版本变动
枚举类型
数字枚举
默认从0开始自增,可以主动设置第一位的值
当前枚举成员的值为它上一个枚举成员的值加1
可主动指定任意位置的值
具有反向映射
字符串枚举
每个成员都必须用字符串字面量
没有自增长行为
没有反向映射
异构枚举
可以混合字符串和数字成员
没有使用场景,并不建议使用
枚举项类型
常数项
key为常数
计算所得项
需要动态计算
如果紧接在计算所得项后面的是未手动赋值的项, 那么它就会因为无法获得初始值而报错
常量枚举
使用const修饰
编译阶段会被删除,枚举成员在使用的地方被内联
不允许包含计算成员
外部枚举
declare 定义的类型只会用于编译时的检查,编译结果中会被删除
类型推论
作用
在有些没有明确指出类型的地方,类型推论会帮助提供类型
发生在初始化变量和成员,设置默认参数值和决定函数返回值时
场景
给与了明确的初始化的值
推断为初始化值的类型
有候选类型
使用候选类型来推断出一个最合适的通用类型

没有一个类型能做为所有候选类型的类型
类型推断为联合类型
上下文类型
根据左侧的类型推断右侧的类型
类型兼容
基本规则
如果x要兼容y,那么y至少具有与x相同的属性
interface Named { name: string; } let x: Named; // y's inferred type is { name: string; location: string; } let y = { name: 'Alice', location: 'Seattle' }; x = y;
等号左边的类型要小于右边
原始类型和对象类型
基本规则
函数类型
参数类型
x的每个参数必须能在y里找到对应类型的参数(只关注参数类型,不关注参数名)
let x = (a: number) => 0; let y = (b: number, s: string) => 0; y = x; // OK x = y; // Error
返回值类型
源函数的返回值类型必须是目标函数返回值类型的子类型
let x = () => ({name: 'Alice'}); let y = () => ({name: 'Alice', location: 'Seattle'}); x = y; // OK y = x; // Error, because x() lacks a location property
函数重载
有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名
枚举
枚举类型与数字类型双向兼容
不同枚举之间不兼容
enum Status { Ready, Waiting }; enum Color { Red, Blue, Green }; let status = Status.Ready; status = Color.Green; // Error
类
基本规则
class Animal { feet: number; constructor(name: string, numFeet: number) { } } class Size { feet: number; constructor(numFeet: number) { } } let a: Animal; let s: Size; a = s; // OK s = a; // OK
类的私有成员和受保护成员会影响兼容性
只有实例的成员会被比较,静态成员和构造函数不在比较的范围内
泛型
指定不同的泛型之后,不同泛型之间不兼容
interface NotEmpty<T> { data: T; } let x: NotEmpty<number>; let y: NotEmpty<string>; x = y; // Error, because x and y are not compatible
没指定泛型类型的泛型参数时,会把所有泛型参数当成any比较
interface Empty<T> { } let x: Empty<number>; let y: Empty<string>; x = y; // OK, because y matches structure of x
模块
术语说明
内部模块
命名空间
外部模块
模块
与es6相同部分
导出
导出声明
export
export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } }
导出语句
对导出的部分重命名
class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } export { ZipCodeValidator }; export { ZipCodeValidator as mainValidator };
重新导出
导入
导入一个模块中的某个导出内容
import { ZipCodeValidator } from "./ZipCodeValidator"; let myValidator = new ZipCodeValidator();
可以对导入内容重命名
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV();
将整个模块导入到一个变量,并通过它来访问模块的导出部分
import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator();
默认导出
一个模块只能够有一个default导出
export =语法
定义一个模块的导出对象
let numberRegexp = /^[0-9]+$/; class ZipCodeValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } export = ZipCodeValidator;
类,接口,命名空间,函数或枚举
导入
import module = require("module")
import zip = require("./ZipCodeValidator"); // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validator = new zip(); // Show whether each string passed each validator strings.forEach(s => { console.log(`"${ s }" - ${ validator.isAcceptable(s) ? "matches" : "does not match" }`); });
模块里不要使用命名空间
装饰器
定义
在类的声明及成员上通过元编程语法添加标注提供了一种方式
使用场景
我们需要额外的特性来支持标注或修改类及其成员
特点
它能够被附加到类声明,方法, 访问符,属性或参数上
注意
装饰器是一项实验性特性,在未来的版本中可能会发生改变
总结
除非特别需求,否则不予考虑
命名空间
声明
namespace
namespace Shape { const pi = Math.PI // 全局可见 export function cricle(r: number){ return pi * r ** 2 } }
特点
在命名空间内可定义任意变量
仅能在命名空间下可见
如果要使命名空间内的变量全局可见,需要使用export关键字导出
可以进行拆分,即一个命名空间可以出现在两个ts文件中
namespace1.ts namespace Shape { const pi = Math.PI // 全局可见 export function cricle(r: number){ return pi * r ** 2 } } namespace2.ts namespace Shape { export function square(x: number){ return x * x } } 这样,相当于一个命名空间分布在两个ts文件中,共享一个命名空间 。
使用
直接使用
// 命名空间 namespace Shape { export function square(x: number){ return x * x } } console.log(Shape.cricle(1)) // namespace1.ts console.log(Shape.square(1)) // namespace2.ts
互相应用,需要三斜杠
/// <reference path="引用文件的相对路径">
/// <reference path="namespace.ts"/> // 命名空间 namespace Shape { export function square(x: number){ return x * x } } console.log(Shape.cricle(1)) // namespace1.ts console.log(Shape.square(1)) // namespace2.ts
在html中引入 typescript 编译后的 js文件
需要把这两个文件都引入
<body> <div class="app"></div> <script src="src/namespace.js"></script> <script src="src/namespace2.js"></script> </body>
别名
在引用命名空间时,可以通过import关键字起一个别名
namespace Biology { export interface Animal { name: string; eat(): void; } } /// <reference path="biology.ts" /> import bio_other = Biology; // 别名
命名空间和模块区别
namespace Validation { // 通过namespace关键词创建一个命名空间 export interface StringValidator { } const lettersRegexp = /^[A-Za-z]+$/; const numberRegexp = /^[0-9]+$/; export class LettersOnlyValidator implements StringValidator { // 要在外部使用必须导出 isAcceptable(s: string) { // 函数内部可以不导出 return lettersRegexp.test(s); } } export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } } // Some samples to try let strings = ["Hello", "98052", "101"]; // 在外界就可以直接通过Validation.StringValidator访问命名空间内部导出的接口 let validators: { [s: string]: Validation.StringValidator; } = {}; // 上面接口的意思是一个对象,对象中的每个成员都是有isAcceptable接口方法的实例化对象 validators["ZIP code"] = new Validation.ZipCodeValidator(); validators["Letters only"] = new Validation.LettersOnlyValidator(); // Show whether each string passed each validator for (let s of strings) { for (let name in validators) { console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`); } }
命名空间:内部模块,主要用于组织代码,避免命名冲突
模块:侧重代码的复用,一个模块里可能会有多个命名空间
混入(Mixins)
将对象组合在一起的一种方法
可以将功能与单独的实现组合在一起
声明合并
合并接口
当多个接口合并时,后面的接口具有更高的优先级
interface Cloner { clone(animal: Animal): Animal; } interface Cloner { clone(animal: Sheep): Sheep; } interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat; }
例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是 单一的字符串字面量, 那么它将会被提升到重载列表的最顶端
interface Document { createElement(tagName: any): Element; } interface Document { createElement(tagName: "div"): HTMLDivElement; createElement(tagName: "span"): HTMLSpanElement; } interface Document { createElement(tagName: string): HTMLElement; createElement(tagName: "canvas"): HTMLCanvasElement; } 合并后的 Document将会像下面这样: interface Document { createElement(tagName: "canvas"): HTMLCanvasElement; createElement(tagName: "div"): HTMLDivElement; createElement(tagName: "span"): HTMLSpanElement; createElement(tagName: string): HTMLElement; createElement(tagName: any): Element; }
合并命名空间
合并命名空间
空间合并
模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口
空间里的值合并
后来的命名空间的导出成员会被加到已经存在的那个模块里
namespace Animals { export class Zebra { } } namespace Animals { export interface Legged { numberOfLegs: number; } export class Dog { } } 等同于: namespace Animals { export interface Legged { numberOfLegs: number; } export class Zebra { } export class Dog { } }
非导出成员
非导出成员仅在其原有的(合并前的)命名空间内可见
合并之后,从其它命名空间合并进来的成员无法访问非导出成员
合并命名空间和类
这让我们可以表示内部类
class Album { label: Album.AlbumLabel; } namespace Album { export class AlbumLabel { } }
也可以使用命名空间为类增加一些静态属性
合并命名空间和函数
创建一个函数稍后扩展它增加一些属性
function buildLabel(name: string): string { return buildLabel.prefix + name + buildLabel.suffix; } namespace buildLabel { export let suffix = ""; export let prefix = "Hello, "; } console.log(buildLabel("Sam Smith"));
TypeScript使用声明合并来达到这个目的并保证类型安全
合并命名空间和枚举
enum Color { red = 1, green = 2, blue = 4 } namespace Color { export function mixColor(colorName: string) { if (colorName == "yellow") { return Color.red + Color.green; } else if (colorName == "white") { return Color.red + Color.green + Color.blue; } else if (colorName == "magenta") { return Color.red + Color.blue; } else if (colorName == "cyan") { return Color.green + Color.blue; } } }
非法的合并
类不能与其它类或变量合并
若想达到类似合并的效果,请考虑混入
模块扩展
打补丁方式
// observable.js export class Observable<T> { // ... implementation left as an exercise for the reader ... } // map.js import { Observable } from "./observable"; Observable.prototype.map = function (f) { // ... another exercise for the reader }
扩展模块方式
// observable.ts stays the same // map.ts import { Observable } from "./observable"; declare module "./observable" { interface Observable<T> { map<U>(f: (x: T) => U): Observable<U>; } } Observable.prototype.map = function (f) { // ... another exercise for the reader } // consumer.ts import { Observable } from "./observable"; import "./map"; let o: Observable<number>; o.map(x => x.toFixed());
不能在扩展中声明新的顶级声明
全局扩展
可以在模块内部添加声明到全局作用域中
// observable.ts export class Observable<T> { // ... still no implementation ... } declare global { interface Array<T> { toObservable(): Observable<T>; } } Array.prototype.toObservable = function () { // ... }