TS是JS的超集,所以JS基础的类型也包含在内。
安装:npm install typescript -g
运行:tsc -v
库的安装
npm i ts-node -g
npm init -y
npm i @types/node -D //声明文件,后面可以直接在终端通过ts-node + index.ts 来打印输出内容
一、基础类型
String、Number、Boolean、undefined、Null 以及ES6的Symbol和ES10的BigInt。
1 | 顶级类型:any、 unknown |
2 | Object |
3 | Number、String、Boolean |
4 | number、string、boolean |
5 | 1 、‘1’ 、false |
6 | never |
any和unnown的区别 :
1、unknown只能赋值给自身或者any类型。
2、unknown没有办法读取任何属性,方法也不可以调用。
2、unkonwn 比 any 更加安全,二者中优先使用unknown类型。
1、字符串类型
//普通声明
let a:string = '1234'
//使用es6的字符串模板
let str:string = `${a}`
可在终端运行:tsc -w (检测变化及编译ts文件为js)
新建一个终端:node + 编译后的js文件名
注:先使用 tsc --nit 进行初始化
2、数字类型
NaN也属于数字类型。
支持十六进制、十进制、八进制和二进制。
let notANumber : number = NaN; //NaN
let num : number = 123; //普通数字
let infinityNumber : number = Infinity; //无穷大
let decimal : number = 6; //十进制
let hex : number = 0xf00d; //十六进制
let binary : number = 0b1010; //二进制
let octal : number = 0o744; //八进制
3、布尔类型
注意:使用构造函数Boolean创造的对象不是布尔值:
事实上new Boolean()返回的是一个Boolean对象,需要改写成:
let createdBoolean : Boolean = new Boolean(1)
//直接使用布尔值
let b1 : boolean = true;
let b2 : boolean = false;
//可以通过函数返回布尔值
let b : boolean = Boolean(1)
JavaScipt没有空值(Void)的概念,在TypeScirpt中,可以用void表示没有任何返回值的函数。
function MyFn ():void {
console.log(‘test void’)
}
void也可以定义undefined和null类型。
let u : void = undefined;
let n : void = null;
出现以上情况,需要在tsconfig.json文件中将严格模式关闭。 “strict”:false
5、undefined和Null类型
与void的区别是,undefined和null是所有类型的子类型。
undefined类型的变量,可以赋值给string类型的变量:
let a : undefined = undefined;
let num1 : string = '1';
num1 = a;
注意:需关闭严格模式。
二、接口和对象类型
TypeScript的核心原则之一是对所有具有的结构进行类型检查。接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
//interface 定义属性
//使用接口约束的时候,不能多属性,也不能少属性
//必须与接口保持一致
interface Person {
a: string;
b: string;
}
//重名interface 可以进行合并
interface Person {
c: number;
}
const person: Person = {
a: "123",
b: "456",
c: 1,
};
1、对象的类型
(1)继承
interface A {
name: string;
}
interface B extends A {
age: number;
}
let person: B = {
name: "玛卡巴卡",
age: 18,
};
(2)可选属性
//可选属性的含义是该属性可以不存在
//所以说这样写也是没问题的
interface Person {
b?:string,
a:string
}
const person:Person = {
a:"213"
(3)任意属性 【propName:string】
注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let person: Person = {
name: "玛卡巴卡",
hobby: "打篮球",
};
(4)只读属性
不允许被赋值,只能读取
//这样写是会报错的
//应为a是只读的不允许重新赋值
interface Person {
b?: string,
readonly a: string,
[propName: string]: any;
}
const person: Person = {
a: "213",
c: "123"
}
person.a = 123
2、添加函数
interface Fn {
(name:string):number[];
}
const fn :Fn = (name:string) {
return [1]
}
三、数组类型
(1)[ ]
let a : number[] = [1,2,3];
let b : string[] = ['1','2','3'];
let c : boolean [] = [true, false];
(2)数组泛型
let a : Array<number> = [1,2,3]
(3)用接口表示数组
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
//表示:只要索引的类型是数字时,那么值的类型必须是数字。
(4)多维数组
let data:number[][] = [[1,2], [3,4]];
//也可以写成
let arr:Array<Array<number>> = [[1,2], [3,4]];
(5)用argument表示函数
function Arr(...args: any): void {
console.log(arguments);
//错误的arguments 是类数组不能这样定义
let arr: number[] = arguments;
}
Arr(111, 222, 333);
function Arr(...args: any): void {
console.log(arguments);
//ts内置对象IArguments 定义
let arr: IArguments = arguments;
}
Arr(111, 222, 333);
//其中 IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
四、函数扩展
(1)函数的类型
//普通函数
function add(a: number, b: number): number {
return a + b;
}
add(1, 2);
//箭头函数
const arr = (a: number, b: number): number => {
return a + b;
};
arr(1, 2);
① 函数的可选参数
const fn = (name: string, age?: number): string => {
return name;
};
fn("玛卡巴卡");
② 函数的默认值
const fn = (name: string = '依古比古', age?: number): string => {
return name;
};
fn();
③ 接口定义函数
//定义参数 num 和 num2 :后面定义返回值的类型
interface Add {
(num: number, num2: number): number
}
const fn: Add = (num: number, num2: number): number => {
return num + num2
}
fn(5, 5)
interface User{
name: string;
age: number;
}
function getUserInfo(user: User): User {
return user
}
④ ts中可以定义this的类型,在js中无法使用,但必须是第一个参数定义this的类型
interface Obj {
user: number[];
add: (this: Obj, num: number) => void;
}
let obj: Obj = {
user: [1, 2, 3],
add(this: Obj, num: number) {
this.user.push(num);
},
};
obj.add(4);
⑤ 定义剩余参数
const fn = (array:number[],...items:any[]):any[] => {
console.log(array,items)
return items
}
let a:number[] = [1,2,3]
fn(a,'4','5','6')
⑥ 函数重载
重载只是方法名字相同,而参数不同,返回类型可以相同也可以不相同 。
如果参数类型不同,则参数类型应设置为any。
参数数量不同可以将不同的参数设置为可选。
let user: number[] = [1, 2, 3, 4];
function findNum(add: number[]): number[]; //如果传入的是一个number类型的数组,则做添加
function findNum(id: number): number[]; //如果传入了id就是单个查询
function findNum(): number[]; //如果没有传入东西就是查询全部
function findNum(ids?: number | number[]): number[] {
if (typeof ids === "number") {
return user.filter((v) => v == ids);
} else if (Array.isArray(ids)) {
user.push(...ids);
return user;
} else {
return user;
}
}
findNum(123);
findNum([4,5,6]);
五、联合类型|类型断言|交叉类型
1、联合类型
let phone : number | string = '010-2785463';
//函数使用联合类型
const fn = (a:number | boolean ):boolean => {
return !! a
}
2、交叉类型
多种类型的集合,联合对象将具有所联合类型的所有成员。
interface People {
name: string;
age: number;
}
interface Man {
sex: number;
}
const person = (man: People & Man): void => {
console.log(man.name);
console.log(man.age);
console.log(man.sex);
};
person({ name: "玛卡巴卡", age: 18, sex: 1 });
3、类型断言
let fn = function (num: number | string) {
console.log((num as string).length);
};
fn("1234");
interface A {
name: string;
}
interface B {
age: number;
}
let fn = (type: A | B) => {
console.log((<A>type).name);
//或者使用以下方法
console.log((type as A).name);
};
//可以使用类型断言来推断他传入的是A接口的值
需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
window.abc = 123
//这样写会报错因为window没有abc这个东西
(window as any).abc = 123
//可以使用any临时断言在 any 类型的变量上,访问任何属性都是允许的。
4、as const
是对字面值的断言,与const直接定义常量是有区别的。
如果是普通类型跟直接const声明是一样的。
const names = '玛卡巴卡'
names = 'aa' //无法修改
let names2 = '玛卡巴卡' as const
names2 = 'aa' //无法修改
// 数组
let a1 = [10, 20] as const;
const a2 = [10, 20];
a1.unshift(30); // 错误,此时已经断言字面量为[10, 20],数据无法做任何修改
a2.unshift(30); // 通过,没有修改指针
5、类型断言不具影响力
将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用 并不会影响结果, 因为编译过程中会删除类型断言。
function toBoolean(something: any): boolean {
return something as boolean;
}
toBoolean(1); // 返回值为 1
六、内置对象&代码雨
JavaScript 中有很多内置对象,它们可以直接在TypeScript 中当做定义好了的类型。
1、ECMAScript的内置对象
Number、Date、Boolean、String、RegExp、Error
let num : Number = new Number(1);
let date : Date = new Date();
let b : Boolean = new Boolean(true);
let s : String = new String;
let reg : RegExp = new RegExp(/\w/);
let error : Error = new Error('错误');
let xhr : XMLHttpRequest = new XMLHttpRequest();
2、DOM和BOM的内置对象
HTMLElement、NodeList、Document、Event等
//DOM
//HTML(元素名称)Element HTMLElement
let body : HTMLElement = document.body;
let allDiv:NodeList = document.querySelectorAll('div');
let div = document.querySelector('div');
//还可以有以下写法:
let div1 :NodeListOf<HTMLDivElement | HTMLElement> = document.querySelectorAll('div');
//BOM
let local:Storage = localStorage;
let lo:Location = location;
let promise:Promise<number> = new Promise((r) =>r(1));
promise.then(res=>{
res.toString
})
let cookie:string = document.cookie;
3、代码雨
index.ts
let canvas: HTMLCanvasElement = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
canvas.width = screen.availWidth;
canvas.height = screen.availHeight;
let str: string[] = "HHHHHHHHH".split("");
let Arr = Array(Math.ceil(canvas.width / 10)).fill(0);
//代码雨
const rain = () => {
ctx.fillStyle = 'rgba(0,0,0,0.05)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#0f0";
Arr.forEach((item, index) => {
ctx.fillText(str[Math.floor(Math.random() * str.length)],index * 10 ,item + 10);
Arr[index] = item > canvas.height || item > 10000 * Math.random () ? 0: item + 10;
});
};
setInterval(rain, 40);
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
padding: 0;
margin:0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./index.js"></script>
</body>
</html>
七、Class
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。基本上,ES6的Class可以看作只是一个语法糖,它的决大步风功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象变成的语法而已。
//定义类
interface Options {
el: string | HTMLElement;
}
interface VueCls {
options: Options;
init(): void;
}
//implements是用来约束class类
class Vue implements VueCls {
options: Options;
constructor(option: Options) {
this.options = option;
}
init(): void {}
}
new Vue({
el: "#app",
});
1、定义类
在TypeScript是不允许直接在constructor定义变量的,需要在constructor上面先声明。
如果定义了变量不使用,也会报错,通常是给一个默认值或者进行赋值。
2、类的修饰符
总共有三个 public 、private、 protected。
① 使用public修饰符可以让定义的变量内部访问,也可以外部方位,如果不写,默认就是public。
② 使用private修饰符,代表定义的变量私有的只能在内部访问,不能在外部访问。
③ 使用protected修饰符,代表定义的变量私有的只能在内部和继承的子类中访问, 不能在外部访问。
3、static静态属性和静态方法
① 使用static定义的属性,不可以通过this去访问,只能通过类名去调用。
② static静态函数同样也是不能通过this去调用,也是通过类名去调用。
③ 需注意:如果两个函数都是static静态,是可以通过this互相调用。
4、interface定义类
TypeScript的interface定义类,是使用关键字 implements 后面跟interface的名字,多个用都好隔开,继承还是使用extends。
interface PersonClass {
get(type: boolean): boolean
}
interface PersonClass2{
set():void,
asd:string
}
class A {
name: string
constructor() {
this.name = "123"
}
}
class Person extends A implements PersonClass,PersonClass2 {
asd: string
constructor() {
super()
this.asd = '123'
}
get(type:boolean) {
return type
}
set () {
}
}
5、抽象类
如果你写的的类实例化之后毫无用处,此时可以把他定义为抽象类。
或者,也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法。
① 例子一:
abstract class A {
public name :string
}
new A (); //报错:无法创建抽象类实例
② 例子二:
在A类定义了getName抽象方法但未实现。
B类实现了A定义的抽象方法,如果不实现就不报错。
我们定义的抽象方法必须在派生类实现。
abstract class A {
name :string;
constructor(name:string){
this.name = name;
}
print():string {
return this.name;
}
}
class B extends A {
constructor() {
super('打篮球')
}
getName():string {
return this.name
}
}
let b = new B();
console.log(b.getName());
八、元祖类型
如果需要一个固定大小的不同类型值的集合,需要使用元组。
1、元组就是数组的变种
元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。
元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。
let arr: [number, string] = [1, "打篮球"];
let arr2: readonly [number, boolean, string, undefined] = [
1,
true,
"打篮球",
undefined,
];
当赋值或访问一个已知索引的元素时,会得到正确的类型:
let arr: [number, string] = [1, "打篮球"];
arr[0].length //报错
arr[1].length //success
//number是没有length的
元组类型还可以支持自定义名称和变为可选的。
let a:[x:number,y?:boolean] = [1]
2、越界元素
对于越界元素,他的类型被限制为联合类型(就是你在元组中定义的类型):
3、应用场景
如定义excel返回的数据:
let excel: [string, string, number, string][] = [
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
['title', 'name', 1, '123'],
]
九、枚举类型
1、数字枚举
例如:红绿蓝 Red = 0 ,Green = 1 ,Blue = 2 分别代表红色为0,绿色为1,蓝色为2
enum Types {
Red,
Green,
Blue
}
这样写就可以实现应为ts定义的枚举中的每一个组员默认都是从0开始的 ,所以也就是
enum Types {
Red = 0,
Green = 1,
Blue = 2
}
//默认就是从0开始的,可以不写值
增长枚举:定义一个数字枚举,Red使用初始化为1,。其余成员会从1开始自动增长。换句话说,Type.Red的值为1,Green为2,Blue为3。
enum Types{
Red = 1,
Green,
BLue
}
2、字符串枚举
在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Types{
Red = 'red',
Green = 'green',
BLue = 'blue'
}
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 ,它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
3、异构枚举
枚举可以混合字符串和数字成员
enum Types{
No = "No",
Yes = 1,
}
4、接口枚举
定义一个枚举Types定义一个接口A,他有属性red值为Types.yyds。
声明对象的时候要遵循这个原则。
enum Types {
yyds,
dddd
}
interface A {
red:Types.yyds
}
let obj:A = {
red:Types.yyds
}
5、const枚举
let 和 var 都是不允许的声明,只能使用const
大多数情况下,枚举是十分有效的方案。然而在某些情况下需求很严格。为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用const枚举。常量枚举通过在枚举上使用const修饰符来定义。
const声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象
const enum Types{
No = "No",
Yes = 1,
}
const 声明编译之后:
普通声明编译之后:
enum Enum {
fall
}
let a = Enum.fall;
console.log(a); //0
let nameOfA = Enum[a];
console.log(nameOfA); //fall
6、反向映射
它包含了正向映射(name-> value)和反向映射(value-> name)
要注意的是:不会为字符串枚举成员生成反向映射。
enum Enum {
fall
}
let a = Enum.fall;
console.log(a); //0
let nameOfA = Enum[a];
console.log(nameOfA); //fail
十、类型推论、类型别名
1、类型推论
① 声明一个变量但是没有定义类型
TypeScript会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
所以TS自动帮我们推断出来这是一个string类型 ,不能够再赋值给别的类型。
② 如果声明变量没有定义类型也没有赋值,这时候TS会推断成any类型,可以进行任何操作
2、类型别名
type关键字(可以给一个类型定义一个名字),多用于复合类型
① 定义类型别名
type str = string;
let s : str = '唱跳RAP都会'
② 定义函数别名
type str = () => string
let s: str = () => "唱跳RAP都会"
③ 定义联合类型别名
type str = string | number
let s: str = 123
let s2: str = '123'
④ 定义值的别名
type value = boolean | 0 | '213'
let s:value = true
//变量s的值 只能是上面value定义的值
type 和 interface的区别:
- interface可以继承,type只能通过&交叉类型合并
- type可以定义联合类型和可以使用一些操作符,但interface不可以。
- interface遇到重名的会合并,但是type不可以。
type 的高级用法:
左边的值会作为右边值的子类型遵循图中上下的包含关系
type a = 1 extends number ? 1 : 0 //1
type a = 1 extends Number ? 1 : 0 //1
type a = 1 extends Object ? 1 : 0 //1
type a = 1 extends any ? 1 : 0 //1
type a = 1 extends unknow ? 1 : 0 //1
type a = 1 extends never ? 1 : 0 //0
十一、never类型
TypeScript将使用never类型来表示不应该存在的状态
// 返回never的函数必须存在无法达到的终点
// 因为必定抛出异常,所以 error 将不会有返回值
function error(message: string): never {
throw new Error(message);
}
// 因为存在死循环,所以 loop 将不会有返回值
function loop(): never {
while (true) {
}
}
1、never和void 的区别
//void类型只是没有返回值 但本身不会出错
function Void():void {
console.log();
}
//只会抛出异常没有返回值
function Never():never {
throw new Error('aaa')
}
//当鼠标移上去的时候,会发现只有void和number,never在联合类型中会被直接移除。
type A = void | number | never
2、never类型的应用场景
type A = '小满' | '大满' | '超大满'
function isXiaoMan(value:A) {
switch (value) {
case "小满":
break
case "大满":
break
case "超大满":
break
default:
//是用于场景兜底逻辑
const error:never = value;
return error
}
}
由于任何类型都不能赋值给never类型的变量,所以当存在进入default分支的可能性时,TS的类型检查会及时帮我们发现这个问题。
十二、Symbol类型
自ECMAScript2015起,symbol成为了一种新的原生类型,就像number和string一样。
symbol类型的值是通过Symbol构造函数创建的。
可以传递参作为唯一标识,只支持string和number类型的参数。
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key
1、Symbol的值是唯一的
const s1 = Symbol()
const s2 = Symbol()
// s1 === s2 =>false
2、用作对象属性的键
let sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
3、使用Symbol定义的属性,是不能通过如下方式遍历拿到:
const symbol1 = Symbol('666')
const symbol2 = Symbol('777')
const obj1= {
[symbol1]: '小满',
[symbol2]: '二蛋',
age: 19,
sex: '女'
}
// 1 for in 遍历
for (const key in obj1) {
// 注意在console看key,是不是没有遍历到symbol1
console.log(key)
}
// 2 Object.keys 遍历
Object.keys(obj1)
console.log(Object.keys(obj1))
// 3 getOwnPropertyNames
console.log(Object.getOwnPropertyNames(obj1))
// 4 JSON.stringfy
console.log(JSON.stringify(obj1))
通过以下方式拿到:
// 1 拿到具体的symbol 属性,对象中有几个就会拿到几个
Object.getOwnPropertySymbols(obj1)
console.log(Object.getOwnPropertySymbols(obj1))
// 2 es6 的 Reflect 拿到对象的所有属性
Reflect.ownKeys(obj1)
console.log(Reflect.ownKeys(obj1))
4、Symbol.iterator迭代器和生成器 for of
支持遍历大部分类型迭代器arr nodeList arguments set map等。
var arr = [1,2,3,4];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); //{ value: 1, done: false }
console.log(iterator.next()); //{ value: 2, done: false }
console.log(iterator.next()); //{ value: 3, done: false }
console.log(iterator.next()); //{ value: 4, done: false }
console.log(iterator.next()); //{ value: undefined, done: true }
测试用例
interface Item {
age: number,
name: string
}
const array: Array<Item> = [{ age: 123, name: "1" }, { age: 123, name: "2" }, { age: 123, name: "3" }]
type mapTypes = string | number
const map:Map<mapTypes,mapTypes> = new Map()
map.set('1','王爷')
map.set('2','陆北')
const obj = {
aaa:123,
bbb:456
}
let set:Set<number> = new Set([1,2,3,4,5,6])
// let it:Iterator<Item> = array[Symbol.iterator]()
const gen = (erg:any): void => {
let it: Iterator<any> = erg[Symbol.iterator]()
let next:any= { done: false }
while (!next.done) {
next = it.next()
if (!next.done) {
console.log(next.value)
}
}
}
gen(array)
我们平时开发中不会手动调用iterator因为他是有语法糖的(for of),记住for of是不能循环对象的,因为对象没有iterator。
for (let value of map) {
console.log(value)
}
数组解构的原理也是调用迭代器
var [a,b,c] = [1,2,3]
var x = [...xxxx]
我们可以自己实现一个迭代器让对象支持for of
const obj = {
max: 5,
current: 0,
[Symbol.iterator]() {
return {
max: this.max,
current: this.current,
next() {
if (this.current == this.max) {
return {
value: undefined,
done: true
}
} else {
return {
value: this.current++,
done: false
}
}
}
}
}
}
console.log([...obj])
for (let val of obj) {
console.log(val);
}
Symbol.hasInstance | 方法。会被instanceof运算符调用。构造器对象用来识别一个对象是否是其实例。 |
Symbol.isConcatSpreadable | 布尔值。表示当在一个对象上调用Array.prototype.concat时,这个对象的数组元素是否可展开。 |
Symbol.iterator | 方法。被for of语句调用。返回对象的默认迭代器。 |
Symbol.match | 方法。被String.prototype.match调用。正则表达式用来匹配字符串。 |
Symbol.replace | 方法。被String.prototype.replace调用。正则表达式用来替换字符串中匹配的子串。 |
Symbol.search | 方法。被String.prototype.search调用。正则表达式返回被匹配部分在字符串中的索引。 |
Symbol.species | 函数值。为一个构造函数,用来创建派生对象。 |
Symbol.split | 方法。被String.prototype.split调用。正则表达式用来分割字符串。 |
Symbol.toPrimitive | 方法。被ToPrimitive抽象操作调用。把对象转换为相应的原始值。 |
Symbol.toStringTag | 方法。被内置方法Object.prototype.toString调用。返回创建对象时默认的字符串描述。 |
Symbol.unscopables | 对象。它自己拥有的属性会被with作用域排除在外。 |
十三、泛型
1、函数泛型
//数字类型函数
function num(a: number, b: number): Array<number> {
return [a, b];
}
num(1, 2);
//字符串类型函数
function str(a: string, b: string): Array<string> {
return [a, b];
}
str("1", "2");
泛型语法:函数名字后面跟一个<参数名>,参数名可以随便写。
当我们使用这个函数的时候,把参数的类型传递进去即可。(也就是动态类型)
function Add<T>(a: T, b: T): Array<T> {
return [a,b]
}
Add<number>(1,2)
Add<string>('1','2')
我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上即可。
function Sub<T,U>(a:T,b:U):Array<T|U> {
const params:Array<T|U> = [a,b]
return params
}
Sub<Boolean,number>(false,1)
2、 定义泛型接口
声明接口的时候,在名字后面加一个<参数>。
使用的时候,传递类型
interface MyInter<T> {
(arg: T): T
}
function fn<T>(arg: T): T {
return arg
}
let result: MyInter<number> = fn
result(123)
3、对象字面量泛型
let foo: { <T>(arg: T): T }
foo = function <T>(arg:T):T {
return arg
}
foo(123)
4、泛型约束
期望在一个泛型的变量上,获取其length参数,但是有的数据类型是没有length属性的,就会报错。
function getLegnth<T>(arg:T) {
return arg.length
}
这时候可以采用泛型约束,约束其为具有length属性的类型,此时会用到interface,代码如下:
interface Len {
length:number
}
function getLegnth<T extends Len>(arg:T) {
return arg.length
}
getLegnth<string>('123')
5、使用keyof约束对象
首先定义T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回类型是联合类型,最后利用extends关键字约束K类型,必须为keyof T联合类型的子类型。
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key]
}
let o = { a: 1, b: 2, c: 3 }
prop(o, 'a')
prop(o, 'd') //此时就会报错发现找不到
6、泛型类
声明方法跟函数类似,名称后面定义<类型>
使用的时候,确定类型new Sub<number>()
class Sub<T>{
attr: T[] = [];
add (a:T):T[] {
return [a]
}
}
let s = new Sub<number>()
s.attr = [1,2,3]
s.add(123)
let str = new Sub<string>()
str.attr = ['1','2','3']
str.add('123')
十四、tsconfig.json配置文件
生成tsconfig.json文件
该文件是通过tsc -- init命令生成的。
配置详解:
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查
"alwaysStrict": true, // 在代码中注入'use strict'
"noImplicitAny": true, // 不允许隐式的any类型
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
"src/**/*"
],
// 指定一个排除列表(include的反向操作)
"exclude": [
"demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
"files": [
"demo.ts"
]
1、include
指定编译文件默认是编译当前目录下所有的ts文件。
2、exclude
指定排除的文件
3、target
指定编译js的版本例如es5 、es6
4、allowJS
是否允许编译js文件
5、removeComments
是否在编译过程中删除文件中的注释
6、rootDir
编译文件的目录
7、outDir
输出的目录
8、sourceMap
代码源文件
9、strict
严格模式
10、module
默认common.js,可选es6模式,amd、umd等
十五、namespace命名空间
我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace避免这个问题出现:
- 内部模块,主要用于组织代码,避免命名冲突。
- 命名空间内的类默认私有。
- 通过export暴露。
- 通过espace关键字定义。
TypeScript与ECMAScript2015一样,任何包含顶级import或者export的文件都被当做一个模块。相反的,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)。
命名空间中通过export将想要暴露的部分导出,如果不用export导出是无法读取其值的。
namespace a {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}
namespace b {
export const Time: number = 1000
export const fn = <T>(arg: T): T => {
return arg
}
fn(Time)
}
a.Time
b.Time
1、嵌套命名空间
namespace a {
export namespace b {
export class Vue {
parameters: string
constructor(parameters: string) {
this.parameters = parameters
}
}
}
}
let v = a.b.Vue
new v('1')
2、 抽离命名空间
a.ts
export namespace V {
export const a = 1
}
b.ts
import {V} from '../observer/index'
console.log(V);
// {a:1}
3、简化命名空间
namespace A {
export namespace B {
export const C = 1
}
}
import X = A.B.C
console.log(X);