前言
个人学习TS的总结, 主要参考官方文档,对于其中不太容易理解的点进行剖析;写下来,主要是自己为了巩固知识点,并给大家分享下,如有不对之处,请指出~
类型系统
基本类型
- 数字(number):包括浮点数,以及 NaN、±Infinity。
- 字符串(string):字符串型。
- 布尔(boolean):布尔型,即 { true, false }。
- null:即 { null }。
- undefined:即 { undefined }。 默认情况下null和undefined是所有类型的子类型,可以赋值给任何类型;当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自
- symbol:符号类型。
- never:表示永远无法到达的终点,是任何类型的子类型,也可以赋值给任何类型,但除自身之外任何类型都不能赋值给它。一个中途抛出错误,或者存在死循环的函数永远不会有返回值,其返回类型是 never
- void:表示没有任何类型,没有返回值;它等于 { null, undefined }。
- 元组(tuple):特殊的数组,指定数组每项具体的类型,一一映射。eg: let tuple1: [number, string] = [12, 'aa']
- 枚举(enum):一组固定值的特定映射。eg: enum sex { MALE, FEMALE} 等同于 enum sex {MALE=0, FEMALE=1} sex.MALE // 取值
- 任意类型(any)
- object:表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。用来描述一种非基础类型,所以一般用在类型校验上,比如作为参数类型。如果参数类型是 object,那么允许任何对象数据传入
另外,所有原始类型的字面量本身也可作为类型使用,其外延只包括自身
高级类型
联合类型
表示一个值可以是几种类型之一。 竖线(|)分隔;若一个值是联合类型,只能访问此联合类型的所有类型里共有的成员
交叉类型
将多个类型合并为一个类型。 竖线(&)分隔;把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,多用于mixins
映射类型
从旧类型创建新类型的一种方式
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
interface Person {
name: string;
age: number;
}
// 调用
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
// 结果
interface PersonPartial {
name?: string;
age?: number;
}
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
类型保护
1、用户自定义类型保护:定义函数,返回类型谓词(语法:parameterName is Type)
2、typeof类型保护:只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename", "typename"必须是 "number", "string", "boolean"或 "symbol" 原始类型,若为除前四个的其他字符串,将不被识别为类型保护
3、instanceof 类型保护:过构造函数来细化类型的一种方式。语法: 实例 instanceof 构造函数
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
return names.map(n => o[n]);
}
interface Person {
name: string;
age: number;
}
let person: Person = {
name: 'Jarid',
age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, ['Jarid]
其中
keyof T: 索引类型查询操作符 (此实例中,为person的属性name、age,值等于'name'|'age')
K extends keyof T:泛型约束
T[K]: 索引访问操作符(此实例中,为person['name'])
interface 与 type 声明类型的区别
定义类型有两种方式: 接口(interface) 和类型别名(type alias), 它们的主要区别如下:
1、interface 只能定义对象类型或者函数, type 还可以定义组合类型,交叉类型(&,类似并集),联合类型(|,类似交集),原始类型
2、interface 方式可以实现接口的 extends 和 implements , 而 type alias 则不行。
3、interface 可以实现接口的合并,但 type alias 则不行。
兼容性
兼容可以简单理解可否赋值,A = B (A 兼容/包含 B)
子类型兼容性
1、Any最“宽”。兼容其它所有类型(换言之,其它类型都是Any的子类型)
2、Never最“窄”。不兼容任何其它类型
3、Void兼容Undefined和Null
4、其它类型都兼容Never和Void
例如:
type point1D = { x: number } // 父 A
type point2D = { x: number, y: number } // 子 B
A = B // ok
当且仅当类型 B 是 A 的子集时,A 兼容 B,B 可以被当成 A 处理
A = B
函数兼容性判定
参数:要求对应参数的类型兼容,数量允许多余 (多的包含少的,即参数允许多,不许少)
返回值:类型兼容 (范围更广, 赋值变量是被赋值的变量的子类型)
类型断言
可以用来手动指定一个值的类型
语法:<类型>值
或 值 as 类型
(在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种。)
使用内置对象作为类型
ECMAScript 内置对象: Error、RegExp、Date、Boolean 等
DOM/BOM 内置对象: Document、HTMLElement、Event、NodeList、MouseEvent 等
参考:
http://kbscript.com/2017/01/2...
https://juejin.im/post/5c2723...
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。类型变量(类型参数) T
泛性约束 extends
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
泛型接口
使用含有泛型的接口来定义函数形状
interface ConfigFn{
<T>(value:T):T;
}
var getData:ConfigFn=function<T>(value:T):T{
return value;
}
getData<string>('张三');
getData<string>(1243); //错误
// 把类型参数提到接口名上
// 写法一:
interface ConfigFn<T>{
(value:T):T;
}
var getData:ConfigFn<string>=function<T>(value:T):T{
return value;
}
getData('20'); /*正确*/
// 写法二:
interface ConfigFn<T>{
(value:T):T;
}
function getData<T>(value:T):T{
return value;
}
var myGetData:ConfigFn<string>=getData;
myGetData('20'); /*正确*/
myGetData(20) //错误
泛型类
使用泛型来定义类
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; };
类
抽象类
抽象类: abstract 修饰,里面可以没有抽象方法。但有抽象方法(abstract method)的类必须声明为抽象类(abstract class)
1、抽象类是不允许被实例化的
2、抽象类中的抽象方法必须被子类实现,不能自己实现
3、可以使用修饰符
注意:如果子类继承的是一个抽象类,子类必须实现父类里的抽象方法,不然的话不能实例化,会报错。
类与接口的关系
类继承类(class extends class)
类实现接口(class implements interface)
接口继承接口(interface extends interface)
接口继承类(interface extends class)
混合类型
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;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
参考: https://ts.xcatliu.com/advanc...
接口
接口的作用:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范;在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
面向对象语言:接口是对行为和动作的抽象,而具体如何行动需要由类(classes)去实现(implement)
ts: 对类的部分行为进行抽象,对对象的形状进行描述; 把不同类中的共性抽取作为接口,再由类分别去实现(implement)
简而言之,定义规范标准,用于复用(与普通类型约束相比)。
interface Person {
readonly id: number; // 只读属性
name: string; // 确定属性
age?: number; // 可选属性
[propName: string]: string; // 任意属性
[index: number]: string; // 可索引属性
(name: string, age: number): void; // 函数
currentTime: Date; // 类类型的属性 (类实现接口)
setTime(d: Date); // 类类型的方法
}
注意:
1、使用 [propName: string] 定义了任意属性取 string 类型的值。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
2、 只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。给对象变量赋值后,只读属性不能再改变。
3、当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内
接口继承接口
interface Shape5 {
color: string;
}
interface Square5 extends Shape5 {
sideLength: number;
}
let square = <Square5>{}; // 定义对象,使用断言
console.log(square) // {}
// let square:Square5 = {color: '', sideLength: 0}
square.color = "blue";
square.sideLength = 10;
console.log(square)
另:一个接口可以继承多个接口
接口继承类
接口继承类时,会继承类的所有成员但不包含实现
class Person {
type: string // ❗️这里是类的描述
}
interface Child extends Person { // ❗️Child 接口继承自 Person 类,因此规范了 type 属性
log(): void
// 这里其实有一个 type: string
}
// ⚠️ 上面的 Child 接口继承了 Person 对 type 的描述,还定义了 Child 接口本身 log 的描述
// 🥇 第一种写法
class Girl implements Child {
type: 'child' // 接口继承自 Person 的
log() {} // 接口本身规范的
}
// 🥈 第二种写法
class Boy extends Person implements Child { // 首先 extends 了 Person 类,然后还需满足 Child 接口的描述
type: 'child'
log() {}
}
当接口继承了一个拥有私有(private)或受保护(protected)的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
class Person {
private type: string // ❗️这里是类的描述
}
interface Child extends Person { // ❗️Child 接口继承自 Person 类,因此规范了 type 属性
log(): void
// 这里其实有一个 type: string
}
// ⚠️ 上面的 Child 接口继承了 Person 对 type 的描述,还定义了 Child 接口本身 log 的描述
// 写法 (only)
class Boy extends Person implements Child { // 首先 extends 了 Person 类,然后还需满足 Child 接口的描述
type: 'child'
log() {}
}
函数
定义函数方式:
let mySum = function (x, y) {
return x + y;
};
1、函数声明
function sum(x: number, y: number): number {
return x + y;
}
2、函数表达式
let mySum = function (x: number, y: number): number {
return x + y;
};
或者
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
可选参数:(?:) 必须接在必需参数后面
参数默认值: 相当于可选参数,任意位置
剩余参数:(...rest)必须是在最后,与 es6 相同
重载
java中方法的重载:重载指的是两个或者两个以上同名函数,但它们的参数不一样,这时会出现函数重载的情况。
typescript中的重载:通过为同一个函数提供多个函数类型定义,一个函数体实现多种功能的目的。
ts为了兼容es5 以及 es6 重载的写法和java中有区别。
重载:同名函数,参数不一样。允许一个函数接受不同数量或类型的参数时,作出不同的处理
function reverse(x: number): number; // 函数定义
function reverse(x: string): string; // 函数定义
function reverse(x: number | string): number | string { // 函数实现
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
多态
父类定义一个方法不去实现,让继承它的子类去实现, 每一个子类有不同的表现(用于继承)
注意:使用多态基础是类的继承或者接口实现。
抽象类
typescript中的抽象类:
1、它是提供其他类继承的基类,不能直接被实例化。
2、用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
3、abstract抽象方法只能放在抽象类里面
用途:抽象类和抽象方法用来定义标准 。 (抽象类:Animal 这个类要求它的子类必须包含eat方法,否则报错;多态:子类继承父类,子类可以不实现该方法,但使用多态就是子类为了实现方法)
模块系统
从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript沿用此概念,在TS 中,模块分为内部模块和外部模块,自typescript 1.5 后,内部模块称作“命名空间”,外部模块称作“模块”
命名空间(namespace)(又名内部模块)
同Java的包、.Net的命名空间一样,TypeScript的命名空间将代码包裹起来,通过export关键字对外暴露需要在外部访问的对象。
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
主要用于组织代码,解决命名冲突,会在全局生成一个对象,定义在namespace内部的类都要通过这个对象的属性访问
模块(module)(又名外部模块)
主要是解决加载依赖关系的,侧重代码的复用。跟文件绑定在一起,一个文件就是一个module,模块写法和ES6一样
三斜线指令
1、书写一个全局变量的声明文件(书写一个全局变量的声明文件时,如果需要引用另一个库的类型)
2、依赖一个全局变量的声明文件
命名空间和模块的区别:
- 命名空间:内部模块,主要用于组织代码,避免命名冲突。
- 模 块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。