横素波而滂流,干青云而直上
1. 语言概览
Typescript是Javascript语言的超集.Javascript程序本身就是合法的Typescript程序。Javascript语言中的所有语法均可以在Typescript语言中使用并具有完全相同的意义。
一些基础语法按下不表,提两个比较常用且新增的语法:
1.1 可选链运算符
Typescript3.7及以上版本中可以直接使用该属性
可选链运算符由一个问号和一个点号组成,即"?."。可选链运算符有以下三种语法形式:
- 可选的静态属性访问
obj?.prop
在该语法中,如果obj的值为undefined或null,那么表达式的求值结果为undefined;否则,表达式的求值结果为obj.prop。
- 可选的计算属性访问
obj?.[expr]
同上,obj的值为undefined或null,那么表达式的求值结果为undefined;否则,表达式的求值结果为obj.prop。
- 可选的函数调用或方法调用
fn?.()
- 短路求值 如果可选链运算符左侧操作数的求值结果为undefined或null,那么右侧的操作数不会再被求值,我们将这种行为称作短路求值。
let x = 0;
let a = undefined;
a?.[++x]; // undefined
x; // 0
值得一提的是,二元逻辑运算符“&&”和“||”也具有短路求值的特性。
1.2 空值合并运算符
a ?? b
该语法中,当且仅当“??”运算符左侧操作数a的值为undefined
或null
(),返回右侧操作数b;否则返回左侧操作数a。
2. 类型基础
2.1 Typescript中的原始类型包含
- boolean
const yes:boolean = true; const no:boolean = false;
- string
const foo:string = 'foo';
- number
const bin:number = 10;
- bigint
const bin:bigint = 0b1010n;
- symbol
const key:symbol = Symbol(); const aUnique: unique symbol = Symbol.for('aUnique');
- undefined
// undefined 类型只能包含一个值,即 undefined const foo:undefined = undefined;
- null
const foo:null = null;
注意: “--strictNullChecks”,即严格的null检查模式。虽然该编译选项的名字中只提及了null,但实际上它同时作用于undefined类型和null类型的类型检查。默认情况下“--strictNullChecks”编译选项没有被启用,这时候除了尾端类型
外的所有类型都是Nullable类型。也就是说,除尾端类型外的所有类型都能够接受undefined
值和null
值。
// --strictNullChecks = false;
let m1: string = undefined;
let m2: number = null;
-
void(表示某个值不存在, 该类型用作函数的返回值类型。)
function log(message: string):void { console.log(message); }
-
枚举类型
enum Direction { Up, // 0 Down, // 1 Left, // 2 Right // 3 }
const 枚举类型
有时候我们不需要使用枚举成员值到枚举成员名的反向映射,因此没有必要生成额外的反向映射代码,所以可以使用const枚举类型
enum E { name, age = 1 } // 编译之后 var E; (function (E) { E[E["name"] = 0] = "name"; E[E["age"] = 1] = "age"; })(E || (E = {})); const enum E1 { name, age } E1.age; // 编译之后 不会有映射关系代码,而是直接做替换; 1 /* E1.age */;
-
字面量类型 Typescript 支持将字面量作为类型使用,我们称之为字面量类型。每一个字面量类型都只有一个可能的值。
2.2 顶端类型
-
any 所有类型都是any类型的子类型。我们可以将任何类型的值赋值给any类型;同时允许将any类型的值赋值给任何其他类型。
let x: any; let a :boolean = x; let b:number = x;
-
unknown 任何其他类型都能赋值给unknown类型,该行为与any行为一致;但是 unknown类型只允许赋值给any类型或者unknown类型。
let x: unknown; let a : any = x; let b: unknown = x; // 会产生编译错误 let c : boolean = x;
2.3 尾端类型(never)
尾端类型是所有其他类型的子类型。
let x :never;
let a : number = x;
正如尾端类型处于最底层,没有类型是尾端类型的子类型,除never类型外,其他类型都不能赋值给never类型。
let x : never ;
x = true // 错误
x = 'ssss' // 错误
2.4 数组类型
2.4.1 类型
const list:number[] = [1,2,3];
const list1: (string | number)[] = ['1',2,3];
const list2: Array<number> = [1,2,3];
const list3: Array<number | string> = ['1',2,3,4];
2.4.2 只读数组
只读数组不允许修改
const rList: ReadonlyArray<number> = [1,2,3];
const rList1: readonly number[] = [1,2,3];
const rList2: Readonly<number[]> = [1,2,3];
2.5 对象类型
2.5.1 Object类型
Object类型注意和Object()构造函数区分;下例是Object()构造函数的类型定义:
interface ObjectConstructor {
readonly prototype: Object;
// 省略了其他成员
}
declare var Object: ObjectConstructor
由此可以看出 Object类型是Object.prototype
的类型。
2.5.2 object类型
object
类型标识非原始类型,即对象类型。关注点不是该对象类型具体包含了哪些属性,因此不允许读取和修改object类型上的自定义属性。
const obj11:object = { name: 'name', age: 18 };
obj11.name // 编译报错:类型“object”上不存在属性“name”
2.5.3 对象类型字面量
const obj: { name: string, age: number } = { name: '111', age: 18 }
const obj1: { name: string, age?:number } = {
name: '111'
}
2.6 函数类型
function add( num1: number, num2: number ) {
return num1 + num2;
}
function add1(x:number, y?:number, z?:number) {
return x + (y??0) + (z ?? 0)
}
// z?:number=0 报错:参数不能包含问号和初始化表达式。
function add2( x:number, y?:number, z?:number = 0 ) {
return x + (y??0) + (z ?? 0)
}
可选参数必须放在函数参数末尾并且可选参数不需要
初始化值。
2.7 接口
类似于对象类型字面量,接口类型也能够表示任意的对象类型。不同的是接口类型能够给对象类型命名以及定义类型参数。
interface IPoint {
x: number,
y: number
}
const point: IPoint = {
x: 1,y: 1
}
2.7.1 字符串索引签名
一个
接口中最多只能定义一个字符串索引签名
。字符串索引签名会约束该对象类型中所有属性
的类型。
interface IA {
[prop: string] : number,
a: number;
b: 0
}
interface IA {
[prop: string] : number,
a: number;
b: string // 错误❌ 类型“string”的属性“b”不能赋给“string”索引类型“number”
}
2.7.2 数值索引签名
一个
接口中最多只能定义一个数值索引签名
。数值索引签名约束了数值属性名对应的属性值的类型。
interface INumA {
[prop: number] : number
}
const arr: INumA = [1];
如果接口中同时存在数值索引签名和字符串索引签名,则数值索引签名的类型要能赋值给字符串索引签名类型。
interface IStrNum {
[prop: string]: number,
[prop:number] : number
}
interface IStrNum {
[prop: string]: number,
[prop:number] : number // 错误❌:“number”索引类型“string”不能分配给“string”索引类型“number”
}
2.7.3 只读属性和方法
readonly 修饰符只允许在属性签名和索引签名上使用。
interface IRead {
readonly a: number
}
const iread:IRead = {
a: 1
}
iread.a = 2; // 错误❌: 无法分配到 "a" ,因为它是只读属性。
如果接口中既定义了只读索引签名又定义了非只读属性签名,那么非只读属性签名定义的属性依旧是非只读的,除此之外的所有属性都是只读的。
interface IRead {
readonly [prop: string]: number,
x: number
}
const iread:IRead = {
x: 1,
y: 2
}
iread.x = 3;
iread.y = 3; // 错误❌: 类型“IRead”中的索引签名仅允许读取
2.7.4 接口的继承
接口的继承需要使用extends关键字,一个接口可以同时继承多个接口没付接口名之间使用逗号分隔。
interface IStyle {
color: string
}
interface IShape {
name: string
}
interface ICircle extends IStyle, IShape {
radius: number
}
const circle1: ICircle = {
color: 'red',
radius: 0.5,
name: 'circle'
}
如果子接口与父接口之间存在同名的类型成员,那么子接口中的类型成员具有更高的优先级。同时,子接口与父接口中的同名类型成员必须是类型兼容的。
interface IStyle {
color: string
}
interface IShape {
name: string
}
interface ICircle extends IStyle, IShape {
radius: number,
name: number // 错误❌:接口“ICircle”错误扩展接口“IShape”。
// 属性“name”的类型不兼容。
// 不能将类型“number”分配给类型“string”
}
2.8 类型别名
类型别名声明则能够为Typescript中的任意类型
命名。
type StringType = string;
type Point = { x: number, y : number }
2.9 类
2.9.1成员可访问性
- public
class Base {
public a :string = "";
}
class Derived extends Base {
public b () {
return this.a // 允许访问
}
}
- protected
受保护成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。类的受保护成员使用protected装饰符标识。
class Base {
protected x :string = "";
a() {
return this.x; // 允许访问
}
}
class Derived extends Base {
public b () {
return this.x // 允许访问
}
}
const base = new Base ();
base.x // 不允许访问
-
private
只允许在当前类的内部类访问,其余情况都不允许访问。
class Base {
private x :string = "";
a() {
return this.x; // 允许访问
}
}
class Derived extends Base {
public b () {
return this.x // 不允许访问
}
}
const base = new Base ();
base.x // 不允许访问
const derived = new Derived();
derived.x // 不允许访问
2.9.2 实现接口
实现接口关键字 implements
interface Color {
color: string;
}
interface Shape {
area(): number;
}
class Circle implements Shape,Color {
color: string = 'red';
radius: number = 1;
area():number {
return Math.PI * this.radius * this.radius;
}
}
2.9.3 抽象类和抽象成员
抽象成员不允许包含任何实现代码
abstract class Ab1 {
abstract message: string = ''; // 属性“message”不能具有初始化表杰式,因为它标记为摘要
}
abstract class Ab1 {
abstract message: string;
}
抽象类可以继承抽象类也可以继承派生类,派生类继承抽象类要实现抽象类中的抽象成员。
abstract class Ab1 {
abstract message: string ;
}
class AbE extends Ab1 {
message = '1'
}
class AbE extends Ab1 { // 不允许 非抽象类“AbE”不会实现继承自“Ab1”类的抽象成员“message”
// message = '1'
}
3. 类型进阶
3.1 泛型
function identity1<T>(arg:T) : T {
return arg;
}
const foo1 = identity1<string>('foo');
3.1.1 可选的类型参数
如果一个形式类型参数没有定义默认类型,那么它是一个必选类型参数
;反之,如果一个形式类型参数定义了默认类型
,那么踏实一个可选
的类型参数。并且必选的类型参数不允许出现在可选类型参数之后
<T = boolean, U> // 不允许
<U, T = boolean> // 正确
3.2 联合类型
一个值的类型可以为若干类型
之一
。
type numericType = number | bigint;
numericType
定义了一个值的类型既可能是number
类型又可能是bigint
类型。
interface Circle {
area: number;
radius: number;
}
interface Rect {
area: number;
width: number;
height: number;
}
type Shape = Circle | Rect;
declare let shape:Shape;
shape.area
shape.radius // 不存在
3.3 交叉类型
一个值可以同时属于
多个类型
。
interface Clickable {
click(): void;
}
interface Focusable {
focus(): void;
}
type T = Clickable & Focusable;
declare let t:T;
t.click(); // 存在
t.focus(); // 存在
原始类型的交叉类型
type U = boolean & number & string; // U 为 never类型
3.4 交叉类型和联合类型
-
优先级
&
优先级高于|
A & B | C & D 等价于 (A & B) | (C & D)
-
分配率性质
A & (B | C) 等价于 (A & B) | (A & C) // 利用上面性质推导下面类型 T = ( string | 0 ) & ( number | 'a' ) = string & number | string & 'a' | 0 & number | 0 & 'a' = never | 'a' | 0 | never = 'a' | 0
3.5 类型断言
<T>
类型断言function add(a:any ) { return <number> a + 1; }
- as T 类型断言
function add(a:any) { return a as number + 1; }
- !类型断言
非空类型断言的一部分,从某个类型中剔除 undefined和 null类型。
function isDefined(value:any) {
return value !== undefined && value !== null;
}
function getLength(v:string | undefined) {
if(!isDefined(v)) {
return 0;
}
return v!.length;
}