数据类型
js的数据类型有七种:Boolean,Number,Object,Array,null,undefined,Symbol
类型
这里就说几个比较特别需要注意的点
空值(Void)
在js中没有空值的概念,在ts中,可以用void表示没有任何返回值的函数
function alertName():void {
alert("my name is lhy");
}
然而声明一个void类型的变量没有什么用,因为你只能将它赋值为null和undefined。
let undisable: void = undefined;
任意类型(any)
如果是一个普通的类型,在赋值过程中改变类型是不允许的,但是任意类型是可以的
let anything: any = {} | null |'' | 0
声明了一个变量为任意值之后,对它的任何操作和返回的内容的类型都是任意值。
并且一个变量如果没有声明任何一个类型,那么它默认就是任意类型。(但是我瞅着好像在我代码里面并不是这样)
联合类型
联合类型使用 | 来分割每个类型。
let test: string | number
访问联合属性的方法:我们必须只能访问此联合类型里公用的属性和方法
错误:
function getLength(something: string | number): number {
return something.length;
}
number没有length这个属性
正确:
function getString(something: string | number): string {
return something.toString();
}
所以,联合类型在被赋值的时候,会根据类型推论的规则推断出一个类型。
对象的类型-interface(接口)
TS中interface是一个很灵活的概念,除了可以用于对类的一部分行为进行抽象以外,也常用于对“对象的形状”进行描述
规范:接口一般首字母大写,有的编程语言中会建议接口的名称加上I前缀
interface IPerson {
name: string;
age: number;
}
let tom: IPerson = {
name: 'Tom',
age: 25
};
这种的接口,则要求赋值的时候,变量的形状必须和接口的形状保持一直,如果想要拥有任意的未添加定义的属性,可以👇
可选属性/任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
这个样子变量就可以拥有任意的未添加的定义属性
只读属性
有的时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用readonly只读属性。
interface IPerson {
readonly id: number;
test: string;
}
let person: IPerson {
id: 123;
test: 'xxx'
}
// 这个时候试图去改变id的值则会报错
person.id = 90;
// 报错!!!
只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
数组的类型
最简单的使用方法就是类型+方括号来表示数组:
let test:number[] = [1, 2, 3, 4]
// 这个数组里面只能是数字
数组泛型( 常用)
let test: Array<number> = [1, 2, 4];
用接口来表示数组
interface NumberArray {
[index: number]: number
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
函数的类型
函数是js里面最重要的成员之一!!
一个函数有输入和输出,要在ts中对其进行约束,则需要把输入和输出都考虑到,最简单的例子:
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;
};
利用接口定义函数的形状
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
此外,对于函数而言,可选参数后面不允许再出现必须参数了
错误:
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
}
// error!!!!!
但是如果我们给参数设置了默认值,那么就不受这个规则的限制了!!
剩余参数
ES6中,可以使用...reset 的方式获取函数中的剩余参数。举例如下:
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
函数重载
重载允许一个函数接受不同数量或类型的参数,作出不同的处理。
比如我们需要实现一个reverse函数,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
利用联合类型,我们可以这么实现:
function reverse(x: number | y: number): 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,前几次都是函数定义,最后一次是函数实现。
类型断言(便捷,不必使用typeof 判断)
之前有说过,当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法,而有时候,我们确实还是需要在还不确定类型的时候就访问其中一个类型的属性或方法,比如
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
此时会报错!!!
/ index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
// index.ts(3,26): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
这个时候我们可以非常方便的使用类型断言
function getLength(something: string | number): number {
// 在需要断言的变量前面增加类型!!!!! 避免写 typeof something === 'string' && something.length 这么复杂了!!!
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
声明文件
通常我们会把声明语句放在一个单独的文件里,这就是声明文件,例如创建一个声明文件jQuery.d.ts,里面的声明语句如下:
declare var JQuery:(selector: string) => any;
一般来说,ts会解析项目中所有的.ts文件,当然也包含以.d.ts结尾的文件,所以当我们将 jQuery.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 jQuery 的类型定义了。
/path/to/project
├── src
| ├── index.ts
| └── jQuery.d.ts
└── tsconfig.json
加入仍然无法解析,那么可以检查下tsconfig.json里面的file, include和exclude的配置。确保其包含了jQuery.d.ts文件。
第三方声明(经常用到)
当然,jQuery 的声明文件不需要我们定义了,社区已经帮我们定义好了:jQuery in DefinitelyTyped。
我们可以直接下载下来使用,但是更推荐的是使用 @types 统一管理第三方库的声明文件。
@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev
可以在这个页面搜索你需要的声明文件。在项目里面,基本上都会用到第三方声明文件。
但是当一个第三方库没有提供我们想要的声明文件的时候,我们需要去手动书写声明文件,这样就得了解声明语句了。👇
手写声明文件
理论上来说,会在项目里面建立一个types文件夹,然后将我们所有的声明文件放在该目录下,然后这种方式需要配置tsconfig.json中的paths和bathUrl字段。
tsconfig.json的配置:
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
}
}
}
内置对象
JS提供的标准对象有:Boolean, error, Date, RegExp等
我们可以在ts中将变量定义为这些类型:
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
DOM和BOM的内置对象
DOM 和 BOM提供的内置对象有:
Document, HTMLElement, Event, NodeList等。
TS中会经常用到这些类型:
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent){
// do something
})
这些类型的定义文件都在ts核心库定义文件
TS核心库定义文件
TS核心库中的定义文件中定义了所有浏览器环境需要用到的类型,并且是预置在TS中的。
当你在使用一些常用方法的时候,TS实际上已经帮你做了很多类型判断的工作。比如:
Math.pow(10, '2');
// index.ts(1,14): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
就是这个函数的两个参数必须都是Number类型
想要用TS写node.js
Node.js不是内置对象的一部分,如果想要用Node.js写TS,则需要引入第三方声明文件:
npm install @types/node --save