那么我们开始吧,
我们从基础语言类型开始
JavaScript 的类型分为两种:原始数据类型(Primitive data types)
和对象类型(Object types)
。
原始数据类型包括:布尔值、数值、字符串、null、undefined
以及 ES6 中的新类型 Symbol。
本节主要介绍前五种原始数据类型在 TypeScript
中的应用。
原始数据类型
布尔值
TypeScript
是用:
来进行类型申请的。
let isDone: boolean = false;
// 编译通过
// 后面约定,未强调编译错误的代码片段,默认为编译通过
注意,使用构造函数 Boolean
创造的对象不是布尔值:
let createdByNewBoolean: boolean = new Boolean(1);
// index.ts(1,5): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
// 后面约定,注释中标出了编译报错的代码片段,表示编译未通过
事实上 new Boolean()
返回的是一个Boolean
对象:
let createdByNewBoolean: Boolean = new Boolean(1);
直接调用 Boolean
也可以返回一个 boolean
类型:
let createdByBoolean: boolean = Boolean(1);
在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样,不再赘述。
下面的类型类似:
数值
let age: number = 23;
字符串
let myName:string = 'Tom';
编译结果为;
var isDone = false;
var age = 23;
var myName = 'Tom';
空值
在 JavaScript
没有空值(Void
)的概念,在 TypeScript
中,可以用 void
表示没有任何返回值的函数:
function alertName(): void {
alert("my name is Tom")
};
声明一个 void
类型的变量没有什么用,因为你只能将它赋值为 undefined
和 null
:
let unusable: void = undefined;
Null和Undefined
这两个原始数据类型比较有意思,他们的类型变量只能赋值为他们本身,undefined
为undefined
,null
只能为null
.
let u: undefined = undefined;
let n: null = null;
不过,它和void
的区别是,他们两个是所有类型的子类型。也就是说undefined
和null
类型的变量,可以赋值给number
类型变量:
// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;
而 void
类型的变量不能赋值给 number
类型的变量:
任意值
任意值(Any)用来表示允许赋值为任意类型
那么什么是任意值类型呢?
如果是一个普通类型,在赋值过程中改变类型是不被允许的:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
编译报错:
hello.ts:25:1 - error TS2322: Type '7' is not assignable to type 'string'.
25 myFavoriteNumber = 7;
~~~~~~~~~~~~~~~~
但是any
类型可以,允许被赋值为任意类型。
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
任意值的属性和方法
在任意值上访问任何属性都是允许的:
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
也允许调用任何方法:
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
可以这样认为,声明一个变量为任意值后,对它的任何操作,返回的内容的类型都是任意值。
未声明类型的变量
如果变量在声明的时候没有指定类型,那么它的默认类型为任意值类型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
相当于
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
类型推论
如果没有明确的指定类型,那么TypeScript
会依照类型推论Type Inference
的规则推断出一个类型。
什么是类型推论呢?
例如:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
它就相当于:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
报的错误都一样,就是说,在你声明变量的时候,如果你没有设置类型,但是你给它指定了具体的值,那么它就会推断它的类型为你制定值的类型。
同理,如果你在声明变量的时候没有指定值,那么它就推断它的类型为any
任意值类型。
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
联合类型
联合类型(Union Types
)表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number = 'seven';
myFavoriteNumber = 7;
同样是上面的代码,你设置了字符串和数字,那么它就可以设置这两种。
let myFavoriteNumber: string | number ;
myFavoriteNumber = false;
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
联合类型是用竖线 |
来分割每个类型。
这里的含义是,你可以是字符串或者数字,但是不可以是其他的类型。
访问联合类型的属性或方法
当TypeScript
不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number {
return something.length;
}
hello.ts:39:22 - error TS2339: Property 'length' does not exist on type 'string
上例中,length
不是string
和number
的共有属性,所以会报错。
访问string
和number
的共有属性是没有问题的:
function getString(something: string | number): string {
return something.toString();
}
联合类型的变量在赋值的时候,会根据类型推论的规则推断出一个类型:
let myFavoriteNumber: string|number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length);
myFavoriteNumber = 7;
console.log(myFavoriteNumber.lenght);
// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
上面的第二行,类型推断字符串,所以length
不报错,
而第四行,类型推断为数字,所以length
为不报错。
对象的类型——接口
这个在java
语言中非常重要的概念,有一个说法是面向接口编程
。在TypeScript
中,我们使用接口interface
来定义对象的类型。
什么是接口
在面向对象语言中,它是对行为的抽象,而具体如何行动需要由类classes
去实现implements
.
而TypeScript
中的接口是一个非常灵活的概念,除了可用于一部分行为进行抽象外,也常用于对象的形状Shape
进行概括。
例如:
interface Person {
name:string;
age:number;
}
let tom:Person = {
name:"Tom",
age:23
};
这个有点想抽象类,我们看到上面代码中,我们创建了一个对象tom
,然后把它的类型设置为接口Person
。这样的话,我们就约束了tom
的形状必须和接口Person
一直。接口一般首字母大写。有的编程语言中会建议接口的名称加上I
为前缀。
因为你设定了对象的类型是接口Person
,所以必须完全一致,少一些属性和多一些属性,通通不允许:
interface Person {
name:string;
age:number;
}
let tom:Person = {
name:"Tom",
};
//hello.ts:59:5 - error TS2322: Type '{ name: string; }' is //not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
interface Person {
name:string;
age:number;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
//hello.ts:62:5 - error TS2322: Type '{ name: string; age: //number; gender: string; }' is not assignable to type //'Person'.
// Object literal may only specify known properties, and //'gender' does not exist in type 'Person'.
所以,赋值的时候,变量的形状必须和接口的形状保持一致。
可是我不想保持一致怎么办呢?可选属性出场
可选属性
可选属性是通过变量后面加一个?
问号来实现的,它的意思是你可以选择这个属性不存在。但是不可以添加未定义的属性。
interface Person {
name:string;
age?:number;
}
let tom:Person = {
name:"Tom",
};
可选属性,你可以存在或者不存在
interface Person {
name:string;
age?:number;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
//hello.ts:62:5 - error TS2322: Type '{ name: string; age: //number; gender: string; }' is not assignable to type //'Person'.
// Object literal may only specify known properties, and //'gender' does not exist in type 'Person'.
不可以添加不存在的属性。
对象的类型 接口的意义就是为了约束对象的创建。
任意类型
有时候我们希望一个借口允许有任意的属性,可以使用下面的方式:
interface Person {
name:string;
age?:number;
[propName: string]:any;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
我们通过[propName: string]
定义了任意属性取string
的值,而我们需要注意的是,一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性。
interface Person {
name:string;
age?:number;
[propName: string]:string;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
我们把上面的any
改为string
,那么不确定属性age
为number
,不属于string
,就报错了。
编译的结果:
hello.ts:56:5 - error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
56 age?:number;
~~~
hello.ts:60:5 - error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
Property 'age' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
60 let tom:Person = {
只读属性
我们还可以设置只读类型,只有在创建的时候赋值,其他的时候不可以赋值,那么就可以使用readonly
定义只读属性了。
interface Person {
readonly id:number;
name:string;
age?:number;
[propName: string]:any;
}
let tom:Person = {
id:12345,
name:"Tom",
age:23,
gender:'male'
};
tom.id = 2222;
//hello.ts:68:5 - error TS2540: Cannot assign to 'id' because //it is a constant or a read-only property.
//68 tom.id = 2222;
类似于上面的代码,我们把readonly
放在属性名称的前面,在上面的tom
对象中,我们创建的时候就已经直接赋值了,所以在后面再次赋值的时候,就报错。
注意:只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。
interface Person {
readonly id:number;
name:string;
age?:number;
[propName: string]:any;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
tom.id = 2222;
hello.ts:61:5 - error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
Property 'id' is missing in type '{ name: string; age: number; gender: string; }'.
61 let tom:Person = {
~~~
hello.ts:68:5 - error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
68 tom.id = 2222;
上面报错有两次,一是在创建对象的时候,没有给只读属性id
赋值,二是它是只读属性,不可以在此赋值。
关于接口行为的约束,我们在后面会有详细的章节。
数组的类型
在TypeScript
中,数组类型有多重定义方式,比较灵活。
「类型 + 方括号」表示法
和java
的类似
let array:number[] = [1,2,3,4,5,6];
let sty_arr:string[] = ['1','2'];
数组中不可以出现其他的数据类型:
let array:number[] = [1,2,3,4,5,6,"1"];
hello.ts:70:35 - error TS2322: Type 'string' is not assignable to type 'number'.
70 let array:number[] = [1,2,3,4,5,6,"1"];
我们试一下是否可以声明联合类型的数组,既包含字符串又包含数字
let com_arr: string | number [] = ["1",1];
hello.ts:73:5 - error TS2322: Type '(string | number)[]' is not assignable to type 'string | number[]'.
Type '(string | number)[]' is not assignable to type 'number[]'.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
73 let com_arr: string | number [] = ["1",1];
看上面错误,这样声明的数组是不行的。需要改成下面这样。
let com_arr: (string | number) [] = ["1",1];
果然可以。666,不过我们还是不要这样用了,你用TypeScript
就是为了约束,你这样玩还不如直接写:
let com_arr = ["1", 1];
数组的泛型
我们也可以用数组的泛型来展示数组。Array
let array: Array = [1,2,3,4,5];
let array1: Array = ["1","2"];
let array3: Array = ["1","2",1];
let array4:Array = [1,"2",{"a":1}];
同样我们可以用any
来定义数组。
关于泛型,可以参考泛型一章.
用接口表示数组
interface NumberArray {
[index:number]:number;
}
// let array:NumberArray = [1,2,3,4];
let array :NumberArray = ["a"];
hello.ts:86:27 - error TS2322: Type 'string' is not assignable to type 'number'.
86 let array :NumberArray = ["a"];
hello.ts:82:5
82 [index:number]:number;
~~~~~~~~~~~~~~~~~~~~~~
The expected type comes from this index signature.
NumberArray
表示:只要index
的类型是number
,那么值必须是number
如果我们设置的数组是字符串的话,那么它会报错。
类数组
类数组不是数组类型,比如内置对象arguments:
function sum() {
let args:number [] = arguments;
}
hello.ts:89:9 - error TS2322: Type 'IArguments' is not assignable to type 'number[]'.
Property 'pop' is missing in type 'IArguments'.
89 let args:number [] = arguments;
我们可以看到上面的错误说的是IArguments
类型无法匹配为nujber []
类型。所以我们可以这样写:
function sum() {
let args:IArguments = arguments;
}
没有报错,妥妥的。
函数的类型
大家要记得,函数是JavaScript的一等公民
函数声明
在JavaScript
中,有两种常见的定义函数的方式--函数声明和函数表达式:
//函数声明 (Function Declaration)
function sum(x,y){
return x+y;
}
//函数表达式 (Function Expression)
let mySum = function(x,y){
return x + y;
}
一个函数有输入和输出,要在 TypeScript
中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:
function sum(x:number,y:number):number {
return x + y;
}
注意:输入多余的或者少输入都会报错。我就不在这里试了。
函数表达式
感觉这里是个坑。
声明一个函数表达式,会写成这样:
let mySum = function (x: number, y: number):number {
return x + y;
}
没有报错,可以编译,实际上上面的代码只对灯油右侧的匿名函数进行了类型定义,而等号左边的mySum
,是对赋值操作进行类型推论出来的。如果我们需要手动为它添加类型,则应该写成这样。
let mySum:(x:number,y:number)=>number = function (x: number, y: number):number {
return x + y;
}
这里不要把TypeScript
的=>
和ES6
的=>
混淆了。
怎么可能不混淆吗?
在TypeScript
的类型定义中,=>
用来表示函数的定义,左边是输入的参数的类型,右边是返回的类型。
在ES6
中,=>
表示箭头函数。不在介绍。
用接口定义函数的形状
那么我们现在用接口来定义个函数所需要的形状:
interface SearchFunc {
(source:string,subString:string):boolean;
}
let mySearch:SearchFunc = function (source:string,subString:string) {
return source.search(subString) !== -1;
}
我们用接口定义了函数的形状,左边是输入的类型,右边是输出的类型。
可选参数
前面我们说道输入多余或者少输入都会报错,那么如何定义和选择的参数呢?与接口中的可选属性类型,我们用?
表示可选的参数:
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了:
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');
// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
参数默认值
TypeScript
中参数的默认值和ES6
一样的写法,TypeScript
会将添加了默认值的参数识别为可选参数:
function buildName(firstName:string,lastName:string = "Cat") {
return firstName +" " +lastName;
}
let tomcat = buildName('Tom','Cat');
let tom = buildName('Tom');
此时就不受可选参数必须接在必须参数后面
的限制了:
function buildName(firstName:string,lastName:string = "Cat") {
return firstName +" " +lastName;
}
let tomcat = buildName('Tom','Cat');
let tom = buildName(undefined,'Tom');
剩余参数
ES6
中,可以使用 ...rest
的方式获取函数中的剩余参数(rest
参数):
function push(array,...items) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a,2,3,4);
我们可以看到,实际上items
是一个数组,所以我们可以用数组的方式来定义它。
function push(array:any [],...items:any []) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a,2,3,4);
需要注意的是,rest
参数只能是最后一个参数。
重载
重载允许一个函数接受不同数量或者不同类型的参数时,做出不同的处理。
比如我们实现下面的函数。利用联合类型,我们可以这样实现。
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('');
}
}
不过这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该是数字,输入字符串的时候,输出也应该是字符串。
我们可以使用多个reverse
的函数类型:
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('');
}
}
上例中,我们重复定义了多次函数reverse
,几次都是函数定义,最后一次是函数实现,在编辑的代码提示中,可以正确的看到前两个提示。
需要注意的是,TypeScript
会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的电影以写在前面。
类型断言
类型断言(Type Assertion) 可以用来手动指定一个值的类型。但是它并不是类型转换。
语法
<类型>值
或
值 as 类型
在tsx
语法中必须用后一种。
例如:将一个联合类型的变量指定为更加具体的的类型。
之前提到过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something:string|number):number {
return something.length;
}
hello.ts:154:22 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
154 return something.length;
我们有时候需要在还不确定类型的时候就访问其中的一个类型的属性或方法,比如:
function getLength(something:string|number):number {
if(something.length){
return something.length;
}else{
return something.toString.length;
}
}
hello.ts:154:17 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
154 if(something.length){
~~~~~~
hello.ts:155:25 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
155 return something.length;
可以看到上面的报错,这个时候我们就可以使用类型断言,将something
断言成string
:
function getLength(something:string|number):number {
if((something).length){
return (something).length;
}else{
return something.toString.length;
}
}
类型断言的用法如上,在需要断言的变量前加上
就可以。
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。
function boBoolean(something:string|number):boolean {
return something;
}
hello.ts:162:12 - error TS2352: Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'number' is not comparable to type 'boolean'.
162 return something;
类型断言不是类型转换,因为你不能把它强制转换为不存在的类型。
声明文件
声明文件官方连接
内置对象
JavaScript
中有很多内置对象,它们可以直接在 TypeScript
中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)
上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
ECMAScript 的内置对象
ECMAScript
标准提供的内置对象有:
Boolean
,String
,Error
,Date
,RegExp
等
我们都可以在TypeScript
中将变量定义为这些类型。
let b:Boolean = new Boolean(1);
let e:Error = new Error("error occurred");
let d:Date = new Date();
let r:RegExp = /[a-z]/;
let s:String = new String("a");
let n:Number = new Number(1);
更多的内置对象,可以查看MDN的文档
而他们的定义文件,则在 TypeScript 核心库的定义文件中。
DOM 和 BOM 的内置对象
DOM和BOM提供的内置对象:
Document、HTMLElement、Event、NodeList
等。
TypeScript
中经常用到这些类型:
let body:HTMLElement = document.body;
let allDiv:NodeList = document.querySelectorAll('div');
document.addEventListener('click',function (e:MouseEvent) {
//DO something
})
它们同样定义在 TypeScript 核心库的定义文件中。
这里是我学习的网址教程
大家加油。。。