TypeScript 全面解读

266 阅读12分钟

一:环境安装与运行

1:环境安装

全局安装:

npm i typescript -g

校验版本:

tsc -v

​ **tsc作用:**负责将 typescript 代码转成浏览器和 node 能识别的 js 代码

2:编译

命令:

tsc filename

示例:

tsc a.js

3:ts-node 编译

全局安装:

npm i ts-node -g

编译:

ts-node filename

示例:

demo.ts

console.log('hello,ts');

利用ts-node编译后,直接可以在控制台输出

hello,ts

二:tsconfig.json配置文件

如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录。 tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。 一个项目可以通过以下方式之一来编译:

三:基础类型

1:布尔类型

let bool: boolean = false;
bool = 'hello';//Error: 不能将类型“string”分配给类型“boolean”。

2:数值类型

let num: number = 123;
num = '123';//Error:不能将类型“string”分配给类型“number”。

3:数组类型

// 写法 ①
let arr1: number[] = [1, 2, 3];
// 写法 ②
let arr2: Array<number> = [4, 5, 6];
// 联合类型
let arr3: (number | string)[] = [7, "8", 9];

4:元组类型

元组类型,类似数组,但是其有两个特点:一是,长度固定,二是,每个元素的类型按顺序一一对应;

// 元组类型
let tuple1:[string,number,boolean];
tuple1= ['a',1,false];

// 长度不一致
let tuple2:[string,number,boolean];
tuple2 = ['a',1,false,'extra'];//Error:源具有 4 个元素,但目标仅允许 3 个。
let tuple3:[string,number,boolean];
tuple3 = ['a',1];//Error:源具有 2 个元素,但目标需要 3 个。

// 类型未对应
let tuple4:[string,number,boolean];
tuple3 = ['a',1,'any'];//Error:不能将类型“string”分配给类型“boolean”。

5:枚举类型

枚举类型就是将一堆变量按照顺序进行枚举出来,一般是从0开始,枚举类型有几个注意点:

  • 枚举名称开头首字母大写;
  • 枚举中属性的值必须为数字;
  • 枚举中属性建议全部大写,按照静态属性的写法;
enum Role { SUPER_ADMIN,ADMIN,USER };

console.log(Role.SUPER_ADMIN);//result:0
console.log(Role.ADMIN);      //result:1
console.log(Role.USER);       //result:2

console.log(Role[0]);         //result:"SUPER_ADMIN"
console.log(Role[1]);         //result:"ADMIN"
console.log(Role[2]);         //result:"USER"

枚举类型,编译成js是这样的写法:

 let Role;
 (function(Role){
     Role[Role['SUPER_ADMIN']=1] ='SUPER_ADMIN';
     Role[Role['ADMIN']=2] = 'ADMIN';
     Role[Role['USER']=3] = 'USER';
 })(Role || (Role = {}));
 console.log(Role);//output:{1: "SUPER_ADMIN", 2: "ADMIN", 3: "USER", SUPER_ADMIN: 1, ADMIN: 2, USER: 3}

这个方法很有用,可以用来code转name和name转code;

let Role;
(function(Role){
    let roles = {SUPER_ADMIN:1,ADMIN:2,USER:3};
    for( let key in roles){ Role[Role[key]=roles[key]] =key;}
})(Role || (Role = {}));
console.log(Role);//output:{1: "SUPER_ADMIN", 2: "ADMIN", 3: "USER", SUPER_ADMIN: 1, ADMIN: 2, USER: 3}

四:接口

作用:对各种值的类型进行检查

1:基本用法

在日常中,我们写一个方法大概是这样的

const getFullName = ({firstName,lastName})=>`${firstName} ${lastName}`;

console.log(getFullName({firstName:'dudu',lastName:'leehom'}));//output:dudu leehom
console.log(getFullName({firstName:'dudu',lastName:2222}));//output:dudu 2222

上述方法中,我们对函数的参数类型没有任何约束,传任意类型都可以;但实际开发中,这是不对的,因为名字只能是String类型的,所以我们需要将上述方法的参数类型做一个约束,这个时候就需要使用接口来定义参数的类型了;

interface NameInfo{
    firstName:string,
    lastName:string
}

const getFullName = ({firstName,lastName}:NameInfo)=>`${firstName} ${lastName}`;

console.log(getFullName({firstName:'dudu',lastName:'leehom'}));//output:dudu leehom
console.log(getFullName({firstName:'dudu',lastName:123}));//error:不能将类型“number”分配给类型“string”

2:可选属性

有时候,我们给一个方法进行传参的时候,有些参数可以不必要传,但接口定义的参数类型必须全部都要传,不传就会报错

interface Vagetable{
    color:string,
    type:string
}

const getVagetable = ({color,type}:Vagetable)=> `This is a ${color?color:''}${type}`;

console.log(getVagetable({type:'tomato'}));
//error:类型 "{ type: string; }" 中缺少属性 "color",但类型 "{ color: any; type: any; }" 中需要该属性。

如果遇到上述情况,我们可以在接口中使用 来设置可选参数

interface Vagetable{
    color?:string,
    type:string
}

const getVagetable = ({color,type}:Vagetable)=> `This is a ${color?color:''}${type}`;

console.log(getVagetable({type:'tomato'}));//output:This is a tomato

3:多余属性检查

有时候,我们会给方法传递一些多余的参数

interface Vagetable{
    color?:string,
    type:string,
    size:number
}

const getVagetable = ({color,type}:Vagetable)=> `This is a ${color?color:''}${type}`;

console.log(getVagetable({ color:'red',type:'tomato'}));
//error: 类型 "{ color: string; type: string; }" 中缺少属性 "size",但类型 "Vagetable" 中需要该属性。

上述方法中,我们给接口定义了三个属性,但在实际使用中我们只传了两个参数,这样就会引起tslint的报错,这时候,我们就需要绕过多余属性,这里我们有三种方法:

①:断言法——利用断言,将实参的类型强制改成对应的接口类型;

interface Vagetable{
    color?:string,
    type:string,
    size:number
}

const getVagetable = ({color,type}:Vagetable)=> `This is a ${color?color:''}${type}`;

console.log(getVagetable({ color:'red',type:'tomato'} as Vagetable));//output:This is a red tomato

②:对象法——定义一个对象,将对象作为实参进行传递;

interface Vagetable{
    color?:string,
    type:string,
    size:number
}

const getVagetable = ({color,type}:Vagetable)=> `This is a ${color?color:' '}${type}`;

const vagetableInfo = {
    color:'red',
    type:'tomato',
    size:18
}

console.log(getVagetable(vagetableInfo));//output:This is a red tomato

③:索引法——利用接口索引属性的方法来声明;

interface Vagetable{
    color?:string,
    type:string,
    [prop:string]:any
}

const getVagetable = ({color,type}:Vagetable)=> `This is a ${color?color:' '}${type}`;

console.log(getVagetable({ color:'red',type:'tomato',size:18}));//output:This is a red tomato

4:只读属性

上面几个例子,都是用接口来定义对象的,这回我们用接口来定义数组;

interface ArrInfo{
    0:string,
    1:number
}

const arr:ArrInfo = ['hello',18];

接口的属性,我们可以给其设置只读属性,设置为只读属性,那么这个属性将不能再被更改,如上述例子中,我们可以将数组的第一位设置为只读属性

interface ArrInfo{
    readonly 0:string,
    1:number
}

const arr:ArrInfo = ['hello',18];
arr[0] = 'dudu';//error:无法分配到 "0" ,因为它是只读属性。

6:函数类型

我们可以用接口来定义函数的类型

interface FuncInfo {
    (x:number,y:number):number
}

const addFunc:FuncInfo=(x,y)=>x+y;

console.log(addFunc(1,2));

除了上述写法之外,我们还有一种类型别名的方式

type FuncInfo=(x:number,y:number)=>number;

const addFunc:FuncInfo=(x,y)=>x+y;

console.log(addFunc(1,2));

7:索引类型

索引类型接口我们在上述绕过多余属性检查中使用过,它可以规定对象中所有属性及属性值的类型,无论其有多少个属性;

interface VageTables{
    [props:string]:number
};

const obj:VageTables = { a:1, b:2, c:3 };

console.log(obj);//output:{ a: 1, b: 2, c: 3 }

上面的接口中,我们定义了属性props类型为String类型,这个props我们可以换成任意字符

8:继承接口

接口的继承是指一个接口继承另一个接口的所有属性;

interface Vagetables{
    color:string
}

interface Tomato extends Vagetables{
    radius:number
}

// 因为Carrot继承了Tomato,而Tomato继承了vagetables,所以其有三个必填属性
interface Carrot extends Tomato{
    sum:number
} 

const tomato:Tomato = {
    radius:5,
    color:'red'
}

const carrot:Carrot = {
    sum:10,
    radius:5,
    color:'red'
}

9:混合类型接口

js中,一个对象可能会有很多类型的属性,如下面这段代码,obj对象具有函数、对象、数组、字符串、数值、布尔多种类型的属性;

const obj = {
    func:()=>{},
    objs:{a:1,b:2},
    arr:[1,2,3],
    str:'dudu',
    num:123,
    bool:true
}

像上述这种拥有复杂类型属性的对象,我们也可以用接口来定义其属性类型;

interface Obj{
    func(arg1:number,arg2:number):number,
    objs:object,
    arr:Array<number>,
    str:string,
    num:number,
    bool:boolean
}

const obj:Obj = {
    func:(x,y)=>x+y,
    objs:{a:1,b:2},
    arr:[1,2,3],
    str:'dudu',
    num:123,
    bool:true
}

console.log(obj.func(1,2));//output:3

还有一种情况,在js中如果我们想写一个函数,每次调用它都会让一个变量的值自增一次,我们需要这样写:

let count = 0;
function add(){
   return ++count;
}

console.log(add());//output:1
console.log(add());//output:2
console.log(add());//output:3
console.log(add());//output:4
console.log(add());//output:5

上面这种写法,有一个弊端,就是声明了一个全局变量,而这个全局变量容易受到污染,比如不小心被重新赋值,因而,我们可以优化一下,采取闭包的方法来处理;

const add = (()=>{
    this.count = 0;
    return ()=>{
        return ++this.count;
    }
})();

console.log(add());//output:1
console.log(add());//output:2
console.log(add());//output:3
console.log(add());//output:4
console.log(add());//output:5

五:函数

1:函数类型

1.1:为函数定义类型
1.2:完整的函数类型
1.3:使用接口定义函数类型
1.4:使用类型别名

2:参数

2.1:可选参数
2.2:默认参数
2.3:剩余参数

3:重载

六:泛型

1:简单使用

在平常中,我们定义一个函数可以是这样的

const getArray = (value,times)=>{
    return new Array(times).fill(value);
}

console.log(getArray('123',5));//output:[ '123', '123', '123', '123', '123' ]
console.log(getArray(123,5));//output:[ 123, 123, 123, 123, 123 ]

在上述方法中,我们会发现,无论我们传入什么类型的数据,都可以生成对应的数组,但,如果我们想给生成的数组一个方法,那么就会碰到一些由于类型错误导致的问题出现

const getArray = (value,times)=>{
    return new Array(times).fill(value);
}

console.log(getArray('123',5).toFixed());//error:类型“any[]”上不存在属性“toFixed”。
console.log(getArray(123,5).map(v=>v.length));//output:[ undefined, undefined, undefined, undefined, undefined ]

从上面我们就可以看出,这都不是我们想要的结果,所以我们需要使用类型约束该方法,避免一些问题的产生,之前我们已经学习过了利用接口、类型别名的方式来给函数的参数做限定,接下来我们回顾一下

//① 使用接口
interface GetArray{
    (value:number,times:number):number[]
}
const getArray:GetArray = (value,times)=>{
    return new Array(times).fill(value);
}

console.log(getArray('123',5));//error:类型“string”的参数不能赋给类型“number”的参数。
console.log(getArray(123,5));//output:[ 123, 123, 123, 123, 123 ]

//② 类型别名
type GetArray = (value:number,times:number) => number[];
const getArray:GetArray = (value,times)=>{
    return new Array(times).fill(value);
}

console.log(getArray('123',5));//error:类型“string”的参数不能赋给类型“number”的参数。
console.log(getArray(123,5));//output:[ 123, 123, 123, 123, 123 ]

现在,我们将使用第三种方法,利用泛型变量来给函数约束类型

const getArray = <T>(value:T,times:number):T[]=>{
    return new Array(times).fill(value);
}
console.log(getArray<number>('123',5));//error:类型“string”的参数不能赋给类型“number”的参数。
//////////////////////////////////////////////////////////////
const getArray = <T>(value:T,times:number):T[]=>{
    return new Array(times).fill(value);
}
console.log(getArray<number[]>([123],5));//output:[ [ 123 ], [ 123 ], [ 123 ], [ 123 ], [ 123 ] ]

2:泛型变量

所谓泛型变量就是我们给一个函数声明一个变量类型,如上述<T>,这个变量是任意类型,我们使用的时候,只需要在调用的时候给这个变量规定一个类型,如上述的 <number>

3:泛型约束

所谓泛型约束就是对泛型变量的类型进行约束,如下面这个方法,如果不做约束,那么传任意类型的数据都可以

const getLen = arg => {
  return arg.length
}
console.log(getLen('hello'));//output:5
console.log(getLen(123));//output:undefined

像上述中,我们就没有约束参数的类型,导致传入数字类型的参数也可以传入,这是不对的,我们需要约束传入的参数必须带有length属性,所以,我们可以利用下面这种方法来约束泛型变量传入的参数

interface ValueWithLength{
    length:number
}

const getLen = <T extends ValueWithLength>(arg:T) =>{
    return arg.length;
}

console.log(getLen('dsaf'));

4:在泛型约束中使用类型参数

所谓在泛型约束中使用类型参数,意思是指,我们将泛型的变量类型约束在我们的一个既定存在的对象中的属性中,如下面这个例子,我们可以传入任意的字符串来生成对应的结果

interface props {
    a:'a',
    b:'b'
}

const getProp = (prop)=>{
    return prop+'hello'
}

console.log(getProp('c'));//output:chello

在上述中,我们如果需要只允许用户传入props中的某个属性作为参数,那么,我们就需要使用类型参数来约束了

interface props {
    a:'a',
    b:'b'
}

const getProp = <T extends keyof props>(prop:T)=>{
    return prop+'hello'
}

console.log(getProp('c'));//error:参数类型不属于a|b
console.log(getProp('a'));//output:ahello

七:ES6中的类(一)

1:ES5和ES6实现创建实例

2:constructor方法

3:类的实例

4:取值函数和存值函数

5:class表达式

6:静态方法

7:实例属性其他写法

8:实现私有方法

八:ES6中的类(二)

1:ES5中的继承

2:ES6中的继承

3:super

3.1:作为函数
3.2:作为对象

4:prototype和proto属性

九:TS中的类

1:基础

2:修饰符

3:readonly修饰符

4:参数属性

5:静态属性

6:可选类属性

7:存取器

8:抽象类

9:实例类型

10:补充

十:枚举

1:数字枚举

数字枚举,就是给枚举中的属性定义一个数字类型的值

enum Direction{
    up = 1,
    down = 3,
    left = 5,
    right = 7,
}

2:反向映射

反向映射,就是把枚举当做一个对象类型的变量,用来获取里面的属性值,如我们获取上述枚举中的left属性的枚举值

enum Direction{
    up = 1,
    down = 3,
    left = 5,
    right = 7,
}

console.log(Direction.left);//output:5

3:字符串枚举

字符串枚举和数字枚举差不多,只不过一个放字符串,一个放数字类型的枚举值,但是字符串枚举必须在赋值的那个属性下,紧邻的属性给一个属性值,可以是任意的

enum Direction{
    up,
    down,
    left = 'LEFT',
    right=1, 
}

console.log(Direction.down,Direction.right);//output:1 1

4:异构枚举

上述例子就是一个异构枚举,也就是所谓的可以放任何类型的属性值

5:枚举成员类型和联合枚举类型

6:运行时的枚举

7:const enum

1:类的基本用法

class GetSum{
    constructor(x,y){
        this.x = x;
        this.y = y;
    }
    sum(){
        console.log(this.x+this.y);
    }
}

const getSum = new GetSum(1,2);

getSum.sum();//output:3

在ES6中的类中,constructor函数是类的构造器,它是实例化类的时候自动执行的方法,不写类也会自动添加这个方法;

2:get & set方法

getset属性是类自带的监听属性的方法,get是获取类中的属性时触发的方法,set是设置类中的属性时触发的方法,一旦使用了 getset

2.1:对象中的get & set
const INFO = {
    _age:18,
    set age(newVal){
        if(newVal>this._age) console.log(`不可能,我才${this._age}岁!`);
        else if(newVal===this._age) console.log(`居然被你猜中了!`);
        else console.log(`嘿嘿,我有那么年轻吗?`);
    },
    get age(){
        return ;
    }
};

INFO.age;
2.2:类中的get & set
class Info{
    constructor(_age){
        this._age = _age;
        if(newVal>this._age) console.log(`不可能,我才${this._age}岁!`);
        else if(newVal===this._age) console.log(`居然被你猜中了!`);
        else console.log(`嘿嘿,我有那么年轻吗?`);
    }
    get age(){
        return '你得到我了!';
    }
}

let info = new Info(18);

console.log( info.age );//output:你得到我了!
info.age = 22;//output:不可能,我才18岁!
info.age = 12;//output:嘿嘿,我有那么年轻吗?
info.age = 18;//output:居然被你猜中了!