前置准备

  • nodejs必安装
  • 全局安装typescript
1
npm install typescript -g
  • 使用tsc命令对ts文件进行编译

    • 进入命名行
    • 进入ts文件所在目录
    • 执行命名tsc xxx.ts即可,xxx.ts中xxx为文件名
      • 如果没有在报错的情况下进行编译,默认情况下依旧会进行编译,但是可以后期配置不编译
    • 编译可以编译为任意js(兼容性处理更加好),后期可以通过配置文件进行配置
  • 文章很多参考和学习这位博主的

ts的基本类型

  • 类型声明是TS非常重要的一个特点

  • 通过类型声明可以指定TS中变量(参数、形参)的类型

  • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错

  • 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值

ts一些类型

类型例子描述
number1, -33, 2.5任意数字
string‘hi’, “hi”, hi任意字符串
booleantrue、false布尔值true或false
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object{name:’孙悟空’}任意的JS对象
array[1,2,3]任意JS数组
tuple[4,5]元组,TS新增类型,固定长度数组
enumenum{A, B}枚举,TS中新增类型

类型声明细节

类型声明(手动)

1
2
3
4
5
6
7
8
9
10
//手动指明类型
let 变量: 类型;

//手动指明类型并赋值
let 变量: 类型 = 值;

//设置函数形参的变量类型和返回值的类型
function fn(参数: 类型, 参数: 类型): 类型{
...
}

类型声明(自动)

  • 变量的声明和赋值是同时进行的,ts就可以自动对变量进行类型检测的
1
2
3
//ts会自动指明类型
//这样子变量类型就会自动根据变量值判断
let 变量 = 变量值;

示例

1
2
3
4
5
// 变量的声明和赋值是同时进行
//指明了是string类型
let typeString = '123';
typeString = "456";
// typeString = false;//报错

类型声明特殊情况-使用字面量

  • 使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
1
2
3
4
5
6
7
8
9
10
11
let zml:10;
zml = 10;
// zml = 100;//报错

let sex: "男" | "女";
sex = "男";
sex = "女";
// sex = "未知";//报错

let color: 'red' | 'blue' | 'black';
let num: 1 | 2 | 3 | 4 | 5;

ts的其他类型说明

number

  • 这个不多说声明

  • 支持进制表示

  • 示例代码

1
2
3
4
5
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

boolean

  • 示例代码
1
2
let isDone: boolean = false;
//let isDone: boolean = 134;报错

string

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    let color: string = "blue";
    color = 'red';

    let fullName: string = `Bob Bobbington`;
    let age: number = 37;
    let sentence: string = `Hello, my name is ${fullName}.

    I'll be ${age + 1} years old next month.`;

any

  • 使用any任意类型,相当于对该变量关闭了ts的类型检测,和普通的js代码一样了

    • 示例代码;
1
2
3
4
5
6
7
8
9
10
11
12
13
 //声明的时候指明类型,显式的any  
let d: any = 4;
d = 'hello';
d = true;

//声明变量的时候不指定类型ts自动判断变量的类型为any,,相当于声明隐式any
let d;

//声明显示的ayn
let anyType:any;

let s:string = '123';
s = anyType;//不会发生报错,因为d的类型是any,会祸害其他人

unknown

  • 示例代码

    1
    2
    3
    4
    5
    let notSure: unknown = 4;
    notSure = 'hello';

    let str:string = '123';
    // str = notSure;//发生报错,unknown不会祸害其他
  • 为什么要使用unknown

    • 第一个原因就是any会祸害其他变量,导致any类型的可以和任何变量兼容,而unknown却做不到

    • 第二个原因就是TypeScript 不允许使用 unknown 的变量,除非将该变量强制转换为已知类型或收窄其类型。(这样子就很好的对变量起到了一个类型的判断)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const x: unknown = 1;
      x*x;//报错 提示对象的类型为 "unknown"

      if(typeof x === 'number'){
      //将该变量强制转换为已知类型或收窄其类型
      //不报错
      console.log(x*x);
      //不报错
      console.log(<number>x * <number>x);
      }


      const x:any = 1;
      x*x;//不会报错

void

  • 表示没有返回值

  • 示例代码

    1
    2
    3
    4
    5
    6
    let unusable: void = undefined;

    //void类型
    function fn1():void{
    console.log("123");
    }

never

  • 表示永远不会返回结果,比如这个函数是用来报错的,所以永远不会返回结果

  • 示例代码

    1
    2
    3
    function error(message: string): never {
    throw new Error(message);
    }

object

  • 不过这种object类型不太经常用,因为很多情况下都是object

  • 比如 typeof null返回值是object

  • 并且ts认为函数也是一个object

  • 示例代码

    1
    let obj: object = {};

{}

  • 用来指定对象中可以包含哪些属性,并且结构要一模一样,多了,少了都不可以!
  • 比如 let b: {name:string} //设定指向一个对象,并且有name属性,并且name属性为string
  • 属性名后面加一个问号,表示这个属性是可选的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//sex为可选
let myObj: { name: string, sex?: string };

myObj = { name: '李白' };//不报错
myObj = { name: '李白', sex: '男' };//不报错

//当然,我们也可以用接口来实现,这样子会更加好看
interface myInter {
name:string;
sex?:string;
}

let myObj2:myInter = {
name:"李白",
sex:"男"
}
  • [propName:string]:any表示任意类型的属性值

    • propName名字可以随便的,这里只是为了区分下,也可以[xxxx:string]:any
    • 当然,如果需要指明value的类型,可以把any改为指定的类型,比如[propName:string]:string 表示value要为string类型
    1
    2
    3
    4
    5
    //表示有必须要有key为name,value类型为string的值,和其他任意数量的任意key(类型必须要为string)和value(类型随意)
    let myObj: { name: string, [propName: string]: any }
    myObj = { 'name': '李白', "sex": '男' };
    myObj = { 'name': '李白', "sex": '男', 'hobby': "吃饭" };//不报错
    myObj = { 'name': '李白', "sex": '男', 'hobby': 555 };//不报错

设置函数结构的类型声明

  • 语法: (形参1:类型1 , 形参2:类型2 [,…] ) => 返回值
  • 比如 希望getSum为函数,并且有二个参数,均为number,并且返回值为number
1
2
3
function getSum(a: number, b: number): number {
return a + b;
}
  • 希望getResult为函数,并且有二个参数,一个为object,另一个为string,并且返回值为number或者string
1
2
3
function getResult(a: object, b: string): number | string {
return a + b;
}

array(数组)

  • 之前的数组的什么都可以存的,在ts当中可以设置存储数据的类型

  • 格式

    • 类型[]
      • 比如let typeNumberArray:number[];创建一个全部类型都是number的数组
    • Array<类型>(数组泛型的表示)
      • 比如let typeNumberArray:Array<number>;创建一个全部类型都是number的数组
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //表示数组里面只可以存储number类型
    let typeNumberArray:number[];
    let typeNumberArray:Array<number>;
    typeNumberArray = [1,2,3];
    // typeNumberArray = [1,2,'3'];//报错


    let a:string[];//代表字符串数组
    let b:number[];//代表数字数组
  • 需要注意的是,数组的一些方法,比如说push也会受到约束

1
2
3
4
5
6
7
8
let myArray:number[] = [1,2,3,4,56];

//无问题
myArray.push(123);

//报错
//类型“string”的参数不能赋给类型“number”的参数
myArray.push('2');

tuple(元组)

  • 元组就是固定长度的数组,里面存储的值多了不可以,少了也不可以

  • 和数组的类型声明不用的就是反转了下,现在[]在前面了,类型在后面了

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    let x: [string, number];
    x = ["hello", 10];

    let h: [string, string];
    h = ['123', '456'];
    h = ['123'];//少了,报错;
    h = ['123', '456', '689'];//多了,报错

enum(枚举)

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    enum Sex {
    Male,
    Female
    }

    //设置一个数组的结构为userName和sex
    let userInfo: { userName: string, sex: Sex };

    userInfo = {
    userName:"李白",
    //使用枚举
    sex:Sex.Male
    }
  • 上面代码编译后的js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
var Sex;
(function (Sex) {
Sex[Sex["Male"] = 0] = "Male";
Sex[Sex["Female"] = 1] = "Female";
})(Sex || (Sex = {}));
//设置一个数组的结构为userName和sex
var userInfo;
userInfo = {
userName: "李白",
//使用枚举
sex: Sex.Male
};

&(表示同时满足条件)

1
2
3
4
5
6
//表示key当中既要有name属性和age属性,并且符合规定的value类型
let all: { name: string } & { age: number };
all = {
name: "李白",
age: 1000
}

|(联合类型)

  • 通过|来分割类型,从而使得某一个变量类型可以为多个
    • 比如如下示例let unionType:string|number;代表的意思就是unionType类型可以为string,也可以为number,但是不能为其他类型
1
2
3
4
5
6
7
let unionType:string|number;
unionType = "我是字符串";
unionType = 123;

//报错
// //不能将类型“boolean”分配给类型“string | number”。
unionType = false;

type(类型别名)

  • 关键字是type
1
2
3
4
5
type myType = 1 | 2 | 3 | 4 | 5 | 6;
let one: myType;
let two: myType;
let three: myType;
let four: myType;

类型断言

  • 有时候二边类型编译器判断是不同,但是我们已经明确知道了二边类型是一样的,这时候就可以使用类型断言

  • 语法 变量 as 类型 或者 <类型>变量

  • 比如: 如果直接将unknown赋值给其他类型的变量,是会发生报错的,所以我们需要类型断言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let notSure: unknown = 'biubiu';

    let strType = '123';

    //不进行断言,会报错
    // strType = notSure;

    //进行类型断言
    strType = notSure as string;
    //或者这种形式的类型断言

    strType = <string> notSure;

    //类型断言
    function getLength2(str:number|string):number{

    // return (str as string).length;
    // 或者
    return (<string>str).length;
    }

非空断言

  • 相当与是多加了一层判断,存在才会执行下去
1
2
3
4
5
// 非空断言
function func3(arg?:string):number{
//这种情况ts就会提示'对象可能为'未定义''
return arg.length;
}

  • 添加了非空断言(相当与是return arg && arg.length)
1
2
3
4
// 非空断言
function func3(arg?:string):number{
return arg!.length;
}

ts编译

自动编译(单个文件编译,无需配置文件)

  • 编译文件的时候,使用 -w指令后,ts编译器会自动监视文件的变化,并在文件发生变化时候对文件进行重新编译
  • 示例: tsc xxx.ts -w
  • 缺点: 只能对一个文件进行ts,因为没有配置文件!

自动编译(整个编译,有配置文件)

  • 配置文件名称为tsconfig.json

  • tsconfig.json是一个JSON文件,里面填写配置文件项

  • 注意: tsconfig.json是可以添加注释的~其他的JSON不可以哦

  • tsconfig.json配置对象如下,官网API

1.include

  • 设置希望被编译文件所在的目录,为一个数组
  • 默认值为: ["**/*"] 代表当前命名行下所有文件夹和文件

示例:

1
2
3
4
{
// 所有src目录和tests目录下的文件和文件夹里面的ts文件,都会被编译
"include": ["src/**/*", "tests/**/*"],
}

了解下 *** 在tsconfig.json的作用

  • **/ 递归匹配任意子目录
  • * 表示匹配任意文件

2.exclude

  • 设置排除列表
  • 默认值:["node_modules", "bower_components", "jspm_packages"]

示例:

1
2
3
4
{
// test目录及下层等的ts文件都不会编译
"exclude": ["./test/**/*"],
}

3.files

  • 指定被编译文件的列表,只有需要编译的文件少时才会用到

示例: 列表中的文件都会被ts编译器所编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
}

4.extends

示例:

1
2
3
4
{
//上述示例中,当前配置文件会自动打包包含config目录下的base.json文件当中的所有信息
"extends":"./config/base",
}

5.compilerOptions(重要)

  • 编译器的选项,比如说编译模式之类的,里面是由keyvalue组成的
  • 下面为常见的key
1.taget
  • 用来指明ts被编译为js的版本,默认值为”es3”
  • 可选值为: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext'.

target设置为es6的效果

1
2
3
4
5
6
7
8
//编译前				//编译后
var a = 123; => var a = 123;
var b = 'true'; => var b = 'true';
let c = '我是动感超人'; => let c = '我是动感超人';
console.log(c); => console.log(c);
setTimeout(()=>{ => {setTimeout(function () {

}, 900); }, 900);
2.module
  • 指明要使用的模块化规范
  • 可选值为:'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'

实例如图: 设置模块化规范为commonjs

index.ts使用es6模块化规范导出say函数,app.ts引入say函数并输出,然后查看编译后的app.js

3.outDir
  • 指定编译后的js文件所在目录
  • 默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置

示例:

1
2
3
4
5
6
{
"compilerOptions": {
// 将编译后的js文件输出到当前配置文件所在目录下的dist文件夹下
"outDir": "./dist",
}
}
4.outFile
  • 将所有的文件编译为一个js文件,默认会将所有编写在全局作用域下的代码合并为一个js文件
  • 如果module配置项指定了none,style或者amd,则会将模块一起合并到文件之中,这几个才可以,es6不可以
5.lib
  • 指明要使用的库
  • 可以写的值
1
'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2020.bigint', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref'.
6.allowJs
  • 是否对js文件进行编译,因为当前目录不止会有ts,可能还有js文件
  • 默认为false,代表不编译js
  • 为true代表编译,为false代表不编译js
7.checkJs
  • 是否对js文件进行检查
  • 默认为false,代表不检查
8.removeComments
  • 是否移除注释
  • 默认为false代表不移除
9.noEmit
  • 不生成编译后的文件
  • 默认为false,代表生成编译后的文件
  • 一般用在只想用ts进行语法检查的情况下使用这个配置项
10.noEmitOnError
  • 当有错误的时候不生成编译后的文件
  • 默认为false,代表ts文件有报错也生成js文件,所以我们经常可以看到有报错还编译生成了js文件就是这个原因

严格检查系列

11.strict(总开关)
  • 开启了这个,下面所有的配置项都会设置为true,默认值为false
12.alwaysStrict
  • 总是以严格模式对代码进行编译

  • 注意:如果使用了es6模块化,那么就自动开启了严格模式,所以编译后不会添加”use strict”关键字

  • 值可以为 true | false

13.noImplicitAny
  • 禁止隐式的any类型,默认为false
  • 设置为true后,隐式的any类型不被允许

14.noImplicitThis
  • 禁止类型不明确的this

如图 所以要指明this的类型 测试了.断言是不可以的忽略这语句的

忽略错误

  • 比如根据id获取,可能会获取不到,但是已经确认了 后面加一个 !
    • var a = document.getElementById(“food”)!;
  • 或者直接@ts-ignore 忽略当前行错误

其他的一些忽略

1
2
3
4
忽略全文
// @ts-nocheck
取消忽略全文
// @ts-check

ts-node(编译运行ts代码)

安装:

1
npm install ts-node -g

使用: xxx.ts为文件名称

ts-node xxx.ts

interface(接口)

  • 接口,我理解为就是一个数据类型,和number,undefined,string等类似

简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Person{
name:string;
age:number;
}
//使用接口
let tom:Person = {
name:"李白",
age:25
}
//多一个,不行
// index.ts(9,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'.
let tom:Person = {
name:"李白",
age:25,
gender:"男"
}

//少一个,也不行
//// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
let tom:Person = {
name:"李白"
}

可选属性

  • 有时我们希望不要完全匹配一个形状,那么可以用可选属性
    • 可选属性的含义是该属性可以不存在
1
2
3
4
5
6
7
8
interface Person {
name:string;
// ?: 代表可选
age?:number;
}
let tom:Person = {
name:"李白"
}
  • 这时仍然不允许添加未定义的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Person {
name: string;
age?: number;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

// examples/playground/index.ts(9,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'.

任意属性

  • 有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
    • 任意属性就是属性名不固定,属性值类型也是不固定
1
2
3
4
5
6
7
8
9
10
interface Person {
name:String;
age?:number;
[propName:string]:any;
}
let tom:Person = {
name:"李白",
//gender:"男" 都可以
//sex:"男" 都可以
}
  • 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//如此示例
//定义了一个可选属性age
//定义了一个任意属性
//所以
//可选属性和确定属性必须要是任意属性的子集才可以
interface Person {
name: string;
age?: number;
[propName: string]: string;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
  • 上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。
  • 另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' } 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; },这是联合类型和接口的结合。
  • 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
1
2
3
4
5
6
7
8
9
10
11
interface Person {
name:string;
age?:number;
[propName:string]:string|number;
}
let tom:Person = {
name:"Tom",
age:25,
gender:'male',
}
//这里就不会报错了,因为可选属性age是联合属性(string,number)的子集,所以不报错

只读属性

  • 有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Person {
//只读属性
readonly id:number;
name:string;
//可选属性
age?:number;
//任意属性
[propName:string]:any;
}

let tom:Person = {
id:8888,
name:"汤姆",
gender:"男"
}


//对只读属性进行修改
//报错信息--无法分配到 "id" ,因为它是只读属性。
tom.id = 9999;

接口表示数组

  • 接口也可以用来描述数组:
  • 注意
    • 这里的[index:number]:number;当中的index和之前的propName一样,可以自定义名称,这里为了便于区分就命名为index
    • 代表的意思就是:当keynumber的时候,所对应的value必须要为number类型
  • 回想下
    • 回想下在之前写到过的[propName: string]: any },代表的就是任意的对象属性
    • 代表的意思就是:当keystring类型的时候,所对应的value必须要为number类型
1
2
3
4
5
interface numberList  {
[index:number]:number;
}

let list:numberList = [1,2,3,4,5];

接口表示一个含函数的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface computed {
param1:string;
param2:string;
getParams():number;
}

// 接口表示
let obj3:computed = {
param1:'123',
param2:'456',
getParams():number{
return 123;
},
}
console.log(obj3);

函数

  • 函数-分为函数的声明,和函数的表达式
    • 函数的声明:函数声明,和var类型的变量一样,会把代码提示到最前面定义(变量提升和函数提升)
    • 函数的表达式:函数在代码执行的时候才会被执行
1
2
3
4
5
6
7
8
9
// 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
return x + y;
};

函数的声明

  • 需要注意的是,函数在typescript当中,声明/定义的时候,参数有几个,使用的时候就应该传入几个,多或者少都不可以
1
2
3
4
5
6
function sum(x: number, y: number): number {
return x + y;
}

//报错 - 应有 2 个参数,但获得 3 个。
sum(1, 2, 3);

函数的表达式

  • 注意不要混淆了 TypeScript 中的=> 和 ES6 中的=>。 在 TypeScript 的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
  • 变量类型(var或则const或者let) 接收的变量名:(参数)=>返回值类型 = 函数表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
const getSum1 = function sum(x: number, y: number): number {
return x + y;
};

console.log(getSum1(1, 2));//输出3

//完整形式如下
const getSum2: (x: number, y: number) => number = function sum(x: number, y: number): number {
return x + y;
};

//格式差不多是这种
const getSum2:(参数)=>返回值类型 = 函数表达式

鼠标放在getSum1getSum2上面,编译器都可以推断出来有什么参数和返回值类型

用接口定义函数的形状

  • 对象,数组接口的区别就是有一个小括号,并且小括号里面可以写多个变量名和类型,并用逗号分割
  • 如下例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 回想,之前定义变量的接口是怎么定义的
interface rule1 {
name: string;
age: number;
sex?: string;
}

let personInfo: rule1 = { name: '李白', age: 18, sex: '男' };

// 数组的话
interface rule2 {
[index: number]: number;
}
let personList: rule2 = [1, 2, 3, 4, 4, 5];

// 接口定义函数的形状

interface rule3 {
(x: number, y: number): number;
}
let fn1: rule3 = function (x: number, y: number): number {
return x + y;
};

剩余参数

  • es6中,可以使用...rest的方式获取函数中的剩余参数
1
2
3
4
5
6
7
8
9
10
function push(array, ...items) {
items.forEach((item) => {
array.push(item);
});
}
let a: any[] = [];
push(a, 1, 2, 3, 4, 5);
//输出[ 1, 2, 3, 4, 5 ]
console.log(a);

  • 事实上,items 是一个数组。所以我们可以用数组的类型来定义它:
1
2
3
4
5
6
7
8
9
10
function push(array:any[],...items:any[]){
items.forEach(item=>{
array.push(item)
});
}

let a = [];
push(a, 1, 2, 3, 4, 5);
//输出[ 1, 2, 3, 4, 5 ]
console.log(a);

泛型

那么什么是泛型呢?

  • 泛型就是值在定义函数,接口,类的时候,不预先指定具体的类型,而是在使用的时候才去指定类型
  • 简单来说就是我现在不去规定你这一个变量的具体类型,而是在使用的时候动态去指明(橡皮泥一样)

如果没有泛型,想一想接收一个什么样子类型的参数,就返回什么类型的参数要怎么做,做法就是如下,一个一个写出来

1
2
3
4
5
6
7
8
9
/* 返回一个number类型的数据,也就是输入number,返回number */
function returnValue(value1:number):number{
return value1;
}

/* 返回一个string类型的数据,也就是输入string,返回string */
function returnValue(value1:string):string{
return value1;
}
  • 但是如果有了泛型,就不用这样子了,我们直接在函数后面添加<T>,然后其他类型改为<T>即可实现泛型
    • 这样子就可以做到上面所说的接收什么类型,就返回什么类型的参数
1
2
3
4
5
6
7
function returnValue<T>(value1:T):T{
return value1;
}
//使用可以指明传入的参数的类型
console.log(returnValue<string>('动感超人'));//输出 动感超人
//也可以干脆不指明
console.log(returnValue(123));//输出 123
  • 再来看看下面案例,我们在函数名的后面添加了<T>,其中T代表任意输入的类型,在后面的输入value:T 和输出Array<T>中即可使用了
1
2
3
4
5
6
7
8
9
10
function createArray<T>(length:number,value:T):Array<T>{
let result:T[] = [];
for(let i = 0; i<length; i++){
result[i] = value;
}
return result;
}
console.log(createArray<string>(3,'x'));//[ 'x', 'x', 'x' ]
//或者
console.log(createArray(3,'x'));//[ 'x', 'x', 'x' ]

多个类型参数

  • 下列函数的功能就是定义一个函数,传入一个二个值的函数,可以交换这二个值
    • 其实你仔细看也不是很复杂,就是类型的定义,
    • changValue后面的<T,U>代表会接收二种未指明类型的数据,分别用TU来表示
    • tuple:[T,U]代表是含有二个值的元组(元组就是固定长度的数组)
    • 函数最后面的:[U,T]代表返回值为一个元组,并且类型要是先U类型,然后在T类型
1
2
3
4
5
6
7
function changeValue<T,U>(tuple:[T,U]):[U,T]{
let tempTuple:[U,T] = [tuple[1],tuple[0]];
return tempTuple;
}

//输出 [ 20, '李白' ]
console.log(changeValue(['李白',20]));

泛型约束

  • 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,使用就需要泛型约束

  • 泛型约束,大白话说就是规定泛型的内容,必须要符合我们规定的才可以

  • 关键字extends和接口的使用

  • 下面这个案例当中,泛型 T 不一定包含属性 length,所以编译的时候报错了。

1
2
3
4
5
function charge<T>(arg:T):T{
//报错,提示 类型“T”上不存在属性“length”
console.log(arg.length);
return arg;
}
  • 这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:
    • 下面案例当中,我们约束了泛型T的范围,使其必须要符合接口lengthWise的形状,也就是必须要包含length属性
    • 这里调用charge("李白")不会报错,是因为"李白"本身是含有length属性的,并且也满足我规定的length属性为number类型
1
2
3
4
5
6
7
8
9
interface lengthWise {
length:number;
}

function charge<T extends lengthWise>(arg:T):T{
console.log(arg.length);
return arg;
}
console.log(charge("李白"));//不报错
  • 此时如果调用 loggingIdentity 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了
1
2
3
4
5
6
7
8
9
10
interface lengthWise {
length:number;
}

function charge<T extends lengthWise>(arg:T):T{
console.log(arg.length);
return arg;
}
console.log(charge("李白"));//不报错
console.log(charge(123));//类型“number”的参数不能赋给类型“lengthWise”的参数

内置对象

  • @参考文章 - 写的很好
  • 最好了解了解内置对象,不然你就会出现如下问题
    • 我通过document.querySelectorAll()获取dom元素,但是返回的变量类型是什么啊?我应该给接收这个变量规定什么类型啊?
    • ……

ECMAScript 的内置对象

1
2
3
4
let b:Boolean = new Boolean(1);
let e:Error = new Error("Error occurred");
let d:Date = new Date();
let r:RegExp = /[a-z]/;

更多的内置对象,可以查看 MDN 的文档

而他们的定义文件,则在 TypeScript 核心库的定义文件中。

DOM 和 BOM 的内置对象

TypeScript 中会经常用到这些类型:

1
2
3
4
5
let body:HTMLElement = document.body;
let allDiv:NodeList = document.querySelectorAll("div");
document.addEventListener("click",function(e:MouseEvent){
//Do something
})

class类

  • 其实我们一些类的思想已经有了,比如
    • 操作浏览器要使用window对象
      • 比如网页的前进后退
    • 操作网页要使用document对象
      • 比如操作网页的dom元素
    • 操作控制台要使用console对象
      • 比如输出错误,输出警告
  • ts当中的的类是作为一种类型存在的

如图,演示了一种Person类型,一种string类型

如图,演示了一种Person类型,一种string类型

类的实例属性

  • 实例属性,就是实例化对象的属性,每一个实例对象当中的实例属性都是独立的
  • 声明实例属性的时候,既可以赋予初始值,也可以后期通过constructor函数来赋初始值
1
2
3
4
5
class Person {
age: number;
sex: string;
gender = '男';
}

类的构造函数

  • 构造函数,就是为实例属性赋予初始值的一个函数!
  • 注意,构造函数当中不需要返回值和返回值类型!
  • 注意,如果没有在constructor上方注明实例属性,那么输入代码的时候可能会没有提示

正确的(含有实例属性)

1
2
3
4
5
6
7
8
9
10
class Person {
age: number;
sex: string;
constructor(age, sex) {
this.age = age;
this.sex = sex;
}
}
var p1 = new Person(100,'男');
console.log(p1.age);

错误的,没有声明实例属性

  • constructor当中,this执行的是新创建的实例对象
  • 所以this.age访问的是实例属性,所以这里提示类型Person上不存在属性age
1
2
3
4
5
6
7
8
9
10
11
class Person {
// age: number;
// sex: string;
constructor(age, sex) {
this.age = age;
this.sex = sex;
}
}
var p1 = new Person(100,'男');
console.log(p1.age);

没有声明实例属性的报错提示

没有声明实例属性的报错提示

类的继承

extends(js,ts都有)

  • 不多说,看代码~关键字extends,记得调用super去调用父类的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//父类
class Animal {
name: string;
food: string;
//叫声
ww: string;
constructor(name: string, food: string, ww: string) {
this.food = food;
this.name = name;
this.ww = ww;
}
say() {
console.log(`我会${this.ww}叫`);
}
}

//子类 - 继承 Animal类
class Dog extends Animal {
constructor(name: string, food: string, ww: string) {
super(name, food, ww);
}
}
var xiaobai = new Dog("小白", "狗粮", "汪汪");
console.log(xiaobai);

可见性修饰符(用于属性或方法)

public

  • 公有:默认值,所有的成员都可以在任何地方访问,不管是继承后的子类,还是自己,都可以
  • 没有说明可见性修饰符则默认是public

protected

  • 保护: 只可以在其声明所在类和子类当中使用(实例化对象不可用)

示例代码: 设置叫声为私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//父类
class Animal {
name: string;
food: string;
//叫声
protected ww: string;//设置为私有属性
constructor(name: string, food: string, ww: string) {
this.food = food;
this.name = name;
this.ww = ww;
}
say() {
console.log(`我会${this.ww}叫`);
}
}

//子类 - 继承 Animal类
class Dog extends Animal {
constructor(name: string, food: string, ww: string) {
super(name, food, ww);
}
}
var xiaobai = new Dog("小白", "狗粮", "汪汪");

//报错
//提示:属性“ww”受保护,只能在类“Animal”及其子类中访问
console.log(xiaobai.ww);

报错提示

报错提示

修饰符用于方法

修饰符用于方法

private

  • 私有:只可以在其声明所在类中使用,其他都不可以使用

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person{
private name: string;
private age: number;

constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}

sayHello(){
console.log(`大家好,我是${this.name}`);
}
}

class Employee extends Person{

constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中不能修改
}
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改

readonly修饰符(只可用于属性)

  • readonly表示只读,用来防止在构造函数之外对属性进制赋值!!!

  • 使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。

  • 接口或者 {} 表示的对象类型,也可以使用readonly

  • 只要是readonly来修饰的属性,必须手动提供明确的类型,否者就成为了一个字面量!

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
//只要是readonly来修饰的属性,必须手动提供明确的类型,否者就成为了一个字面量!
readonly age: number = 18;
constructor(age: number) {
this.age = age;
}
}

var p1 = new Person(100);
console.log(p1);
//报错,提示无法分配到 "age" ,因为它是只读属性
p1.age = 200;

示例代码:用于{}

1
2
3
let obj: { readonly name: string } = { name: 'jack' };
//报错,提示无法分配到 "name" ,因为它是只读属性。
obj.name = 'Mike';

接口(interface)

  • 接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的换句话说接口中的所有方法都是抽象方法接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
  • 可以使用接口对对象进行约束
  • 也可以使用接口对类进行约束

示例:使用接口对对象进行约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Person {
name: string;
//规定要有一个函数为'sayHello',并且没有返回值
sayHello(): void;
}

//规定这个per的对象结构为Person定义的结构
function fn1(per: Person) {
//调用per当中的sayHello
per.sayHello();
}
var myObj = {
name: "孙悟空",
//es6的写法
// sayHello(){
// console.log("叫我齐天大圣!");
// }
//或者这种es5的写法也可以
sayHello: function () {
console.log("叫我齐天大圣!");
}
}

fn1(myObj);

示例:使用接口对类进行约束(接口的实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Person {
name: string;
sayHello(): void;
}

class Student implements Person {
constructor(public name: string) {
}

sayHello() {
console.log('大家好,我是' + this.name);
}
}

示例1:接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 接口的实现

interface computed {
param1:string;
param2:string;
getParams():number;
}

class myFn implements computed {
param1: string;
param2:string;
getParams():number{
return 123;
}
constructor(param1:string,param2:string){
this.param1 = param1;
this.param2 = param2;
}
}

const one = new myFn('123','456');
console.log(one.getParams());

抽象类/抽象方法

  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
  • 抽象类中可以有抽象方法普通方法
  • 抽象方法只能定义在抽象类当中
  • 子类必须要对抽象类的抽象方法进行重写

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Animals {
//定义抽象方法
abstract run(): void;
say() {
console.log("动物在叫");
}
}
class Dog extends Animals {
run() {
console.log("狗会汪汪叫");
}
}

var xiaobai = new Dog();
xiaobai.run();//输出:狗会汪汪叫
xiaobai.say();//输出:动物在叫

泛型(Generic)

  • 定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

  • 格式,在括号前面添加<>

  • 比如一个函数,我要求有一个参数,用户传入这个参数的类型就是这个函数返回值的类型,那要怎么做?使用泛型

不推荐使用any!

1
2
3
4
//!不推荐使用any!
function test(arg: any): any{
return arg;
}

使用泛型

  • 这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。
1
2
3
function test<T>(arg: T) {
return arg;
}

那么如何使用泛型定义的函数?

  • 方式一:直接使用
  • 使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
1
2
3
4
5
6
7
8
9
function test<T>(arg: T) {
return arg;
}

//方式一(直接使用):
console.log(test("李白"));//输出:李白
console.log(test(123));//输出:123


  • 方式二:指明类型
1
2
3
4
5
6
7
function test<T>(arg: T) {
return arg;
}

// 方式二:指明类型
console.log(test<string>('杜甫'));//输出:杜甫
console.log(test<boolean>(true));//输出:true
  • 可以同时指定多个泛型,泛型间使用逗号隔开:
1
2
3
4
5
function test2<T, K>(arg1: T, arg2: K): K {
return arg2;
}

test2<number, string>(100, '杜甫');
  • 类中同样可以使用泛型:
1
2
3
4
5
6
7
class MyClass<T>{
prop: T;

constructor(prop: T){
this.prop = prop;
}
}
  • 除此之外,也可以对泛型的范围进行约束

使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。

1
2
3
4
5
6
7
interface MyInter{
length: number;
}

function test<T extends MyInter>(arg: T): number{
return arg.length;
}

其他

函数默认值

1
2
3
4
// 设置name 默认值为'李白' age为'18'
function sayHello(name: string = "李白", age: string = "18") {
console.log("我的名字叫",name,"年龄为",age);
}