使用TypeScript开发的程序更安全,常见的错误都能检查出来。TS能让程序员事半功倍。而原因在于TS的“类型安全”(借助类型避免程序做无效的事情)。
图 运行程序的过程
但是TS不会直接编译成字节码,而是编译成JavaScript代码。TS编译器生产AST后,先对代码做类型检查,然后再编译成JavaScript代码。
图 TS运行程序的过程
1 类型
类型是指一系列值及对其执行的操作。
图 TS的类型层次结构
TS具有自动推导类型的功能:
let num1 = 1; // 推导为number类型
我们可以不要显式的指明num1为number类型(let num1:number = 1),这是一种推荐的写法。
1.1 unknown与any
unknown | 表示任何值,但是TS会要求你再做检查,细化类型。 |
any | 表示任何值,TS不会做检查,应尽量避免使用。 |
表 unknown与 any的对比
unknown类型的值可以比较(使用==、===、||、&&和?)、可以否定(使用!)及可以使用typeof和instanceof。
let a: unknown = 30; //unknown
let b = a === 123; // boolean
let c = a + 10; //Error,Object is of type ‘unknown’
if (typeof a === ‘number’) {
let d = a + 10; // number
}
unknown的用法如下:
1)TS不会把任何值推导为unknown类型,必须显示注解。
2)unknown类型的值是可以比较。
3)操作时不能假定unknown类型的值为某种特定类型,必须先向TS证明这个值确实是某个类型。
any 应尽量避免使用。在极少数情况下,any将发挥作用:
let a: any = 66;
let b: any = ['danger'];
let c = a + b; // string 66danger 如果上面不显式指明为any类型,则会报编译错误
1.2 boolean
boolean类型有两个值:true和false。该类型的值可以比较(==、===、||、&&和?)、可以否定(!)。用法如下:
let a = true; // boolean
const b = true; // ture
let c: boolean = false; // boolean
let d: true = true; // true
let e: true = false; // error Type false is not assignable to type true
1.2.1 推导类型的四种方式
1)让TS推导出值的类型为boolean(a)。
2)让TS推导出值为某个具体的布尔值(b)。
3)明确告诉TS,值的类型为boolean(c)。
4)明确告诉TS,值为某个具体的类型值(d和e)。
第四种情况,称为类型字面量。即仅表示一个
let a: object = {
x: 'a'
};
a = {name:'212'};
console.log(a); // {x:'212'}
a = false; // error Type boolean is not assignable to type object
a.name; // error,Property name does not exist on type object
let b: Object = {
name: 'b'
};
b = true;
b.name; // Property name does not exist on type Object
值的类型。注意,这种情况下也仍需为变量赋值,否则变量为空。
1.3 对象
object | 接受所有引用类型,除基本类型(string、number、boolean、undefined和null)外,都可以赋值给它。 |
Object | 是一个通用类型,可以被赋予任何类型的值。赋值以后能改变类型。 |
{} | Object的别名。 |
表 object、Object与{}
这三种只能表示该值是一个JS对象,范围比较窄,尽量避免使用。
1.3.1 对象字母量
上面的a指定为object类型后,在引用属性name时提示并没有该字段。这时,我们可以采用让TS自行推导的方式:
let a = {
name: 'hello'
}; //类型为 {name: string}
console.log(a.name); // hello
这种声明对象类型的方式称为对象字面量句法(“这个东西的结构是这样的”)。可以让TS推导对象结构,也可以在花括号内明确描述:
let a: {name: string} = {
name : 'a'
};
a = {
name: "b"
};
a = {
name: 'c',
age: 17 // error Type { name: string; age: number; } is not assignable to type { name: string; }
}
如果先声明变量,然后在初始化,TS将确保在使用该变量时已经明确为其赋值了。
1.3.2 属性可选
let a: {
att1: number,
att2?: string, //该属性可以没有
[key: number]: boolean // 可能有任意多个数字属性,其值为boolean类型。(key这个名字可以是任意的)
}
a = {att1: 1};
a = {att1: 2, att2: 'hello'};
a = {att1: 1, 1: true,2: false};
上面[key:T]: U 句法称为索引签名,通过这种方式告诉TS,指定的对象可能有更多的键。键的类型为T,值的类型为U。其中T必须为number或string类型。key可以是任意名称。
1.4 数组与元组
TS支持两种注解数组类型的句法:T[]和Array<T>。两者作用和性能无异。
let arr: string[] = ["hello","TS"];
let arr2: Array<string> = ["hello","TS"];
let arr3: any[] = [1,"hello",false]; // 一般情况,数组应该保持同质,即类型一样
any[]离开定义时所在的所用域后,TS将最终确定一个类型,不再扩张。
function buildArr() {
let a = []; // any[]
a = [1,false];
return a;
}
let newArray = buildArr();
newArray.push(2);
newArray.push("hello"); // 运行时报错 Argument of type '"hello"' is not assignable to parameter of type 'number | boolean'
1.4.1 元组
元组是array的子类型。长度固定,各索引位的值具有固定的已知类型。与其他类型不同,声明元组时必须显式注解类型:
let a: [string,number,boolean];
元素也支持可选的元素(?表示可选)。也支持剩余元素,即为元组定义最小长度:
let a: [string,boolean?];
a = ["hello"];
a = ['hello', false];
let b: [boolean,...string[]];
b = [true];
b = [true,'hello'];
1.4.2 只读数组和元组
let a: readonly string[] = ["a","b","c"];
a.concat("s");
console.log(a.concat("s")); // [ 'a', 'b', 'c', 's' ]
console.log(a); // ["a","b","c"];
a.push("x"); // Property push does not exist on type readonly string[]
若想更改只读数组,使用非变型方法,例如.concat和.slice。不能使用可边型方法,例如.push和.splice。
1.5 其他类型
处理较长的数字时,为了便于标识数字,建议使用数组分隔符:
let a = 1_000_000
let c : 1_233_344 = 1_233_344; //注意,这里对c一定要赋值,如果是:let c : 1_233_344,这样值为空
null | 缺少值。 |
undefined | 尚未赋值的变量。 |
void | 没有return语句的函数。 |
never | 永不返回的函数。 |
表 TS中的空值
1.5.1 枚举
使用枚举极易导致安全问题,建议远离枚举。
1)枚举可以分几次声明:
enum Language {
Chinese,
English
}
enum Language {
Russian = 2 //此时必须为这个枚举指定一个与上面枚举值不同的值
}
console.log(Language[1]); // English
console.log(Language.English); // 1
// Language:
// {
// '0': 'Chinese',
// '1': 'English',
// '2': 'Russian',
// Chinese: 0,
// English: 1,
// Russian: 2
// }
2)枚举值可以混用字符串和数字。(在枚举中,尽量不要使用数字,否则可能会置枚举于不安全的境地。)
enum Language {
Chinese= 1,
English= 'e'
}
// { '1': 'Chinese', Chinese: 1, English: 'e' }
3)为避免不安全的访问操作,可以通过const enum指定枚举的安全子集。来限制只能通过字符串来访问。
enum Language {
Chinese= 1,
English= 'e'
}
console.log(Language[3]); // undefined 可以访问,这样操作不安全。
const enum Language2 {
Chinese= 1,
English= 'e'
}
Language2[2]; //error const类型只能通过字符串类型访问,即Language2.Chinese
2 语言特性
2.1 类型别名 type
可以使用变量声明(let、const)为值声明别名,类似的,可以为类型声明别名:
type Age = number; // number的别名为Age
这样可以减少重复输入复杂的类型及更清楚地表明变量的作用。
别名具有以下特性:
1)使用别名的地方都可以替换成源类型。
2)同一个类型别名不能声明两次。
3)别名采用块级作用域。内部的类型别名将掩盖外部的类型别名。
type Age = 18;
{
type Age = string;
}
type Age = boolean; // Duplicate identifier Age
2.2 并集和交集
type A = {name: string,age: number};
type B = {name: string,file: boolean};
type C = A | B; // {name: string,age:number,file: boolean}
type D = A & B; // {name: string}