文章目录
ts简介
JavaScript是一门动态弱类型语言。对变量的类型非常宽容,没有类型约束。
TS是一个拥有类型系统的JavaScript超集,可以编译成纯JavaScript。
ts主要提供了类型系统和对ES6的支持。
tsconfig.json目录中文件的存在表明该目录是ts项目的根。该tsconfig.json文件指定了根文件和编译项目所需的编译器选项。
有node环境,通过npm安装:
安装:
npm i -g typescript
检查ts版本:
tsc -v
TS命令:
tsc 文件名 (运行文件)
三个要点:
-
类型检查
TS会在编译代码时,进行严格的静态类型检查。
-
语言扩展
异步操作、装饰器、接口类型、抽象类。
-
工具属性
TS可以编译成标准的JavaScript。
ts好处:
重塑“类型思维”
- 大部分的函数看看类型的定义就可以知道如何使用;
- 可以在编译阶段就发现大部分错误
- 增加了编辑器的功能,包括代码补全,接口提示、跳转到定义等。
- Ts增加了代码的可读性和可维护性。
强类型语言和弱类型语言
- 强类型语言:不允许改变变量的数据类型,除非进行强制类型转换。
- 弱类型语言:变量可以被赋予不同的数据类型。
动态类型语言和静态类型语言
- 静态类型语言:在编译阶段确定所有变量的类型。
- 动态类型语言:在执行阶段确定所有变量的类型。
静态类型与动态类型对比:
静态类型语言 | 动态类型语言 |
---|---|
对类型极度严格 | 对类型非常宽松 |
立即发现错误 | Bug可能隐藏数月甚至数年 |
运行时性能好 | 运行时性能差 |
自文档话 | 可读性差 |
动态类型语言的支持者认为:
- 性能是可以改善的(V8引擎),而语言的灵活性更重要
- 隐藏的错误可以通过单元测试发现
- 文档可以通过工具生成
强类型语言:不允许程序在发生错误后继续执行。
总结:ts是一门强类型静态类型语言。
TS基本数据类型 (所有数据都声明它的类型)
Boolean Number String Array Function Object Symbol undefined null
void any never 元祖 枚举 高级类型
类型注解:
作用:相当于强类型语言中的类型声明
语法:
(变量/函数):type
类型推论
ts会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查。
联合类型
number | string 联合类型:取值可以为多种类型中的一种 分隔符 |
原始类型:布尔、字符串、数字
let bool: boolean = true
let num: number = 123
let str: string = ‘abc’
数组
let tuple:[number, string] = [1,’1’]
- 「类型+方括号」表示
在元素类型后面接上[ ],表示由此类型元素组成的一个数组:
let arr1:number[ ] = [1,2,3]
let fibonacci: number[] = [1, 1, 2, 3, 5];
数组的一些方法的参数会根据数组在定义时约定的类型进行限制:
数组的项中不允许出现其他的类型:以下是错误的。
let fibonacci: number[] = [1, '1', 2, 3, 5];
// Type 'string' is not assignable to type 'number'.
- 数组泛型
可以使用数组泛型(Array Generic) Array< > 来表示数组:
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
- 类数组
类数组不是数组类型,比如arguments实际上是一个类数组,不能用普通的数组的方式来描述,而是应该用接口:
any在数组中的应用
用any表示数组中允许出现任意类型:
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。数组泛型来定义返回值的类型。
T用来指代任意输入的类型。
多个类型参数,定义泛型的时候,可以一次定义多个类型参数
泛型约束
- 用接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。
对象 声明类型的对象
let obj:{x:number, y:number} = {x:1,y:2}
在ts中不可以任意修改对象的属性,以上写法就可以修改对象的属性了。
- 对象的类型-----接口
在ts中,使用接口来定义对象的类型。 对行为的抽象,具体如何行动需要由类去实现。
ts中的接口可用于对类的一部分行为进行抽象以外,也常用于对对象的形状进行描述。
约束tom的形状必须和接口Person一致。接口一般首字母大写。定义的变量比接口少了一些属性、多一些属性都是不允许的。
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
元组
元组合并了不同类型的对象
越界元素:当访问超出元组长度的元素时,它的类型会被限制为元组中每个类型的联合类型。
可选属性
赋值的时候,变量的形状必须和接口的形状保持一致。
如果不希望完全匹配一个形状,可用可选属性 ?。可选属性的含义是该属性可以不存在。仍然不允许添加未定义的属性。
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
任意属性
使用 [propName: string] 定义了任意属性取 string 类型的值。
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集。
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
一个接口只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number;
[propName: string]: string | number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
只读属性
一些字段只能在创建的时候被赋值,那么可以用readonly定义只读属性。
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
symbol 唯一(es6)
let s1:symbol = Symbol( )
let s2 = Symbol( )
undefined null
都只能赋值为undefined null。undefined类型的变量只能被赋值为undefined,null类型的变量只能被赋值为null。
修改tsconfig.json中的strictNullChecks为false,即可把其他类型赋值为undefined和null,因为在官方文档中,undefined和null是其他类型的子类
void
没有任何返回值的函数
let noReturn = ( ) => { }
any
任意类型,变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。任意类型,即在任意值上访问任何属性和方法都是允许的,即不做类型检查。
never
永远不会有返回值的类型。never类型表示的是永不存在的值的类型。比如never类型是总会抛出异常或者根本就不会有返回值的函数表达式或箭头函数表达式的返回值的类型。变量也可能是never类型,当它们被永不为真的类型保护所约束时。
never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除never本身以外)即使any也不可以赋值给never
枚举
一组具有名字的常量集合 enum
数字枚举、字符串枚举、异构枚举(字符串、数字混用)
枚举类型用于取值被限定在一定范围内的场景:枚举使用enum关键字来定义。枚举成员被赋值从0开始递增的数字,同时也会对枚举值到枚举名进行反向映射。未手动赋值的枚举项与手动赋值的重复了,ts不会察觉到这一点。
枚举项有两种类型:常数项和计算所得项
当满足以下条件,枚举成员被当作是常数:(不具有初始化函数并且之前的枚举成员是常数)
- 在这种情况下,当前枚举成员的值为上一个枚举成员的值+1。但第一个枚举元素是个例外,它没有初始化方法,初始值为0。
- 枚举成员使用常数枚举表达式初始化。常数枚举表达式是ts表达式的子集,它可以在编译阶段求值。
- 常数枚举用const enum定义枚举类型:常数枚举在编译阶段被删除,并且不能包含计算成员。
- 外部枚举用declare enum定义的枚举类型:declare定义的类型只会用于编译时的检查,编译结果中会被删除。
常数枚举编译前:
编译后:
函数 声明类型
let add = (x:number,y:number) => x + y
let compute: (x:number, y:number) => number
compute = (a,b) => a+b;
函数的类型
函数声明:在JavaScript中,定义函数的方式:函数声明和函数表达式。
// 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}
// 函数表达式(Function Expression)
let mySum = function (x, y) {
return x + y;
};
在ts的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
在ts中,输入多余的或者少于要求的参数,是不被允许的
function sum(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 (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
参数默认值
ts会将添加了默认值的参数识别为可选参数
剩余参数
items是一个数组。可以用数组的类型来定义它。
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
利用联合类型。重复定义了多次函数reverse,前几次都是函数定义,最后一次是函数实现。
function reverse(x: string): string;
function reverse(x: number): number;
function reverse(x: number | string): any {
if(x === 10) {
console.log(x);
return x;
}else if( x === 'lss') {
console.log(x);
return x;
}
};
reverse('lss');
reverse(10);
类型别名(常用于联合类型)
类型别名用来给一个类型起一个新名字。
字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个。
类型别名与字符串字面量类型都是使用type进行定义。
类型断言
可以用来手动指定一个值的类型(两种语法)
值 as 类型 (常用)
<类型> 值
在react中必需使用as
类型断言的用途:
- 将一个联合类型断言为其中一个类型
- 将一个父类断言为更加具体的子类
- 将任何一个类型断言为any
- 将any断言为一个具体的类型
前提条件:
要使得A能够被断言为B,只需要A兼容B或者B兼容A即可。
泛型
是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
在函数名后添加了,其中T用来指代任意输入的类型,在后面的输入value:T和输出Array中即可使用了。
定义泛型的时候,可以一次定义多个类型参数:
双重断言 as any as Foo
!!!除非迫不得已,否则不要使用双重断言
类型断言VS类型转换
类型断言只会影响ts编译时的类型,类型断言语句在编译结果中会被删除,类型断言不是类型转换,不会真的影响到变量的类型。
类型断言VS类型声明
核心区别:
a断言b,只需要满足a兼容b或者b兼容a即可。
A赋值c,需要满足b兼容a
类型声明比类型断言更加严格。为了增加代码的质量,最好优先使用类型声明,也比类型断言的as语法更加优雅。
类型断言VS泛型
声明文件
在使用第三方库时,引出它的声明文件,才能获得对应的代码补全、接口提示等功能
- declare var 声明全局变量
- declare function 声明全局方法
- declare class 声明全局类
- declare enum 声明全局枚举类型
- declare namespace 声明(含有子属性的)全局对象
- interface 和 type 声明全局类型
- export 导出变量
- export namespace 导出(含有子属性的)对象
- export default ES6默认导出
- export = commonjs 导出模块
- export as namespace UMD库声明全局变量
- declare global 扩展全局变量
- declare module 扩展模块
- /// 三斜线指令
声明文件必需以.d.ts为后缀
类相关概念: - 类Class:定义了一件事物的抽象特点,包含它的属性和方法。
- 对象Object:类的实例,通过new生成。
- 面向对象OOP:封装、继承、多态。
- 封装:将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据。
- 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性。
- 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。
- 存取器:用以改变属性的读取和赋值行为。
- 修饰符:修饰符是一些关键字,用于限定成员或类型的性质。比如public表示公有属性或方法。
- 抽象类:抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口:不同类之间公有的属性或方法,可以抽象成一个接口,接口可以被类实现。一个类只能继承自另一个类,但是可以实现多个接口。
Es6中类的用法 - 属性和方法:使用class定义类,使用constructor定义构造函数
- 通过new生成新实例,会自动调用构造函数
- 类的继承:使用extends关键字实现继承,子类中使用super关键字来调用父类的构造函数和方法
- 存取器:使用getter和setter可以改变属性的赋值和读取行为:
- 静态方法:使用static修饰符修饰的方法称为静态方法,不需要实例化,直接通过类来调用。
Es7中类的用法 - 实例属性:通过构造函数this.xxx来定义
- 静态属性:使用static定义一个静态属性
Ts中类的用法
piblic private和protected 三种访问修饰符
- public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有属性和方法都是public。
- private修饰的属性或方法是私有的,不能在声明它的类的外部访问。该类不允许被继承或者实例化。在子类中不允许访问的。
- protected修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的。该类只允许被继承。允许在子类中访问。
- readonly 只读属性关键字,只允许出现在属性声明或者索引前面或者构造函数中。如果readonly和其他访问修饰符同时存在的话,需要写在后面。
抽象类
abstract 用于定义抽象类和其中的抽象方法
抽象类不允许被实例化。抽象类中的抽象方法必须被子类实现。
类实现接口
接口的另一个用途:对类的一部分行为进行抽象。
一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。
一个类可以实现多个接口
接口继承类,在常见的面向对象语言中,接口不能继承类,在ts中可以(原因:在声明class point时,会创建一个名为point的类,还会创建名为point的类型“实例的类型”)
也就是说,接口继承类和接口继承接口没有什么本质的区别。
合并
声明合并:定义了两个相同名字的函数、接口或类,那么会合并成一个类型;
函数的合并:使用重载定义多个函数类型
接口的合并:接口中的属性在合并时会简单的合并到一个接口中:
相当于
合并的属性的类型必须是唯一的
类的合并与接口的合并规则一致。
内置对象
js中有很多内置对象,它们可以直接在ts中当作定义好的类型。
内置对象是指根据标准在全局作用域上存在的对象。标准是指ECMAScript和其他环境的标准。
es的内置对象:
boolean、error、date、正则等
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
BOM和DOM
Document、HTMLElement、Event、NodeList等
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
用TS写node.js
Node不是内置对象的一部分,用ts写node,需要引入第三方声明文件:
npm install @types/node --save-dev
小问题:
元组越界问题:ts版本3.9.3
正常元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。当访问一个已知索引的元素,可以得到正确的类型。当访问一个越界的元素,会使用联合类型替代。
解决:越界的元素,当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型,不可以是不存在的类型。不可以直接给不存在的索引进行赋值。