【JavaScript】TypeScript总结
TypeScript
TypeScript ?
- 以 JavaScript 为基础构建的语言
- 一个 JavaScript 的超集
- 可以在任何支持 JavaScript 的平台中执行(TS 不能被 JS 解析器直接执行,可以编译为任意版本的 JS )
- TypeScript 扩展了 JavaScript 并添加了类型
TypeScript 增加了什么?
- 类型
- 添加 ES 不具备的新特性
- 丰富的配置选项
- 强大的开发工具
TypeScript 开发环境搭建
- 使用 npm 全局安装 typescript:
npm i typescript
- 使用 tsc 对 ts 文件进行编译:
tsc xxx.ts
类型声明
-
类型声明是 TS 非常重要的一个特点
-
通过类型声明可以指定 TS 中变量(参数、形参)的类型
-
指定类型后,当为变量赋值时,TS 编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
-
简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
-
语法:
let 变量: 类型 let 变量: 类型 = 值 function fn(参数: 类型, 参数: 类型): 返回值类型 { ... }
-
自动类型判断
- TS 拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS 编译器会自动判断变量的类型
- 所以如果你的变量的声明和赋值是同时进行的,可以省略掉类型声明
-
类型
- number(任意数字:1,2,3)
- string(任意字符串:'hi')
- boolean(布尔值)
- 字面量(限制变量的值就是该字面量的值)
- any(任意类型:*)
- unknown(类型安全的 any:*)
- void(没有值或 undefined:空值 undefined)
- never(不能是任何值:没有值)
- object(任意的 JS 对象:{name:'ABing'})
- array(任意的 JS 数组:[1,2,3])
- tuple(元素,TS 新增类型,固定长度数组:[4,5])
- enum(枚举,TS 中新增类型:enum(A,B))
// 也可以直接使用字面量进行类型声明 let a: 10 a = 10 // 可以使用 | 来连接多个类型(联合类型) let b: 'male' | 'female' b = 'female' let c: boolean | string | number c = true c = 'hello' c = 222 // any 标识的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测 // 使用TS时,不建议使用any类型 let d: any // 声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any(隐式any) // let d d = 10 d = 'hello' d = true // unknown 表示未知类型的值 let e: unknown e = 10 e = 'hello' e = true // any类型,可以赋值给任意变量(建议使用unknown) let s: string s = d // unknown实际上就是要给类型安全的any,unknown类型的变量,不能直接赋值给其他变量 // s = e // 必须先进行类型判断再进行赋值 if (typeof e === 'string') { s = e } // 或者使用 类型断言:可以用来告诉解析器变量的实际类型 /** * 语法: * 变量 as 类型 * <类型>变量 */ s = e as string s = <string>e // void用来表示空,以函数为例,就表示没有返回值 function fn(): void { return null } // never表示永远不会返回结果 function fn2(): never { throw new Error('error') }
// object表示一个js对象 let a: object a = {} a = function () {} // {}用来指定对象中可以包含哪些属性 // 语法:{属性名:属性值,属性名:属性值} // 在属性名后面加上 ? 表示属性时可选属性 let b: { name: string; age?: number } // b = {} b = { name: 'ABing' } // [propName:string]:any 表示任意类型的属性 let c: { name: string; [propName: string]: any } c = { name: 'ABing', age: 20, gender: '男' } /** * 设置函数结构的类型声明 * 语法:(形参:类型,形参:类型,...) => 返回值 */ let d: (a: number, b: number) => number d = function (n1: number, n2: number): number { return n1 + n2 } /** * 数组的类型声明: * 类型[] * Array<类型> */ // string[]表示字符串数组 let e: string[] e = ['a', 'b'] // number[]表示数值数组 let f: number[] let g: Array<number> g = [1, 2, 3] /** * 元组:固定长度的数组(存储效率较数组高) * 语法: * [类型,类型,类型] */ let h: [string, number] // h = ['1', '2', 3] h = ['1', 2] /** * enum枚举 */ enum Gender { Male, Female, } let i: { name: string; gender: Gender } i = { name: 'ABing', gender: Gender.Male, } console.log(i.gender === Gender.Male) // | 表示或,& 表示与 let j: { name: string } & { age: number } j = { name: 'ABing', age: 20 } // 类型别名 type myType = 1 | 2 | 3 let k: myType let l: myType
编译选项(tsconfig.json)
{
/*
tsconfig.json是ts编译器的配置文件,ts编译器可以根据它的信息来对代码进行编译
*/
// 用来指定哪些ts文件需要被编译,* 表示任意文件,** 表示任意目录
"include": ["./src/**/*"],
/*
表示不需要被编译的文件目录
默认值:["node_modules","bower_components","jspm_packages"]
*/
"exclude": ["./src/hello/**/*"],
// extends表示被集成的配置文件
// "extends": "",
// files指定被编译文件的列表,类似include,只有需要编译的文件少时才会使用
// "files": [],
/*
编译器选项
*/
"compilerOptions": {
// 用来指定ts被编译为的版本,默认ES3,ESNext表示最新版本ES
"target": "ESNext",
// 指定要使用的模块化的规范
"module": "commonjs",
// lib用来指定项目中要使用的库
"lib": ["DOM"],
// outDir用来指定编译后的文件所在目录
"outDir": "./dist",
// 将代码合并为一个文件
// 设置outFile后,所有的全局作用域中的代码会合并到同一个文件中
"outFile": "./dist/app.js",
// 是否对js文件进行编译,默认是false
"allowJs": false,
// 是否检查js代码是否符合代码规范,默认false
"checkJs": false,
// 是否移除注释
"removeComments": false,
// 不生成编译后的文件(适用于只是用tsc检查语法而不生成编译文件的场景)
"noEmit": false,
// 当有错误时,不生成编译文件
"noEmitOnError": false,
// 所有严格检查,建议设置为true
"strict": false,
// 用来设置编译后的文件是否使用 严格模式 ,默认false
"alwaysStrict": false,
// 不允许隐式any,默认false
"noImplicitAny": false,
// 不允许不明确类型的this,默认false
"noImplicitThis": false,
// 严格检查空值
"strictNullChecks": false
}
}
搭配 webpack 使用
-
安装相关依赖:
npm i typescript ts-loader -D
-
配置打包规则
<!-- webpack.config.js --> // 指定要加载的规则 rules: [ { // test指定规则生效的文件 test: /\.ts$/, // 要使用的loader use: 'ts-loader', // 要排除的文件 exclude: /node-modules/, }, ],
面向对象
-
类(class)
// 使用class关键字来定义一个类 /** * 对象中主要包含两个方法 * 属性 * 方法 */ class Person { /** * 直接定义的属性是实例属性,需要通过对象的实例去访问 * const per = new Person() * per.name * * 使用static开头的属性是静态属性(类属性),可以直接通过类去访问 * Person.age * * readonly开头的属性表示一个只读属性,无法修改 */ // 定义实例属性 // readonly name: string = 'ABing' name = 'ABing' // 在属性前使用static关键字可以定义类属性(静态属性) // static readonly age: number = 20 age = 20 // 定义方法 /** * 如果方法以static开头则方法就是类方法,可以直接通过类去调用 * 否则为实例方法,通过实例调用 */ sayHello() { console.log('Hello') } } const per = new Person() console.log(per.name) // 静态属性要通过类去访问 // console.log(Person.age) // per.name = 'abing' // console.log(per.name) per.sayHello()
-
构造函数
class Dog { name: string age: number // constructor 构造函数 // 构造函数会在对象创建时调用 constructor(name: string, age: number) { // 在实例方法中,this就表示当前的实例 // 在构造函数中,当前对象就是当前新创建的那个对象 // 可以通过this向新建的对象中添加属性 this.name = name this.age = age } bark() { // 在方法中可以通过this来表示当前调用方法的对象 console.log(this) } } const dog = new Dog('a', 2) const dog2 = new Dog('b', 3) console.log(dog) console.log(dog2) dog.bark() dog2.bark()
-
继承
;(function () { // 定义一个Animal类 class Animal { name: string age: number constructor(name: string, age: number) { this.name = name this.age = age } sayHello() { console.log(this.name + '在叫') } } /** * Animal称为父类,Dog、Cat称为子类 * 使用继承后,子类将会拥有父类所有的方法和属性 * 通过继承可以将多个类中共有的代码写在一个父类中,这样只需要写一次即可让所有的子类都同时拥有父类中的属性 * 如果希望在子类中添加一些父类中没有的属性或方法可以直接添加 * 如果在子类中添加了和父类相同的方法,则子类方法会覆盖父类方法(这种子类覆盖父类方法的形式,我们叫做方法的重写) */ // 定义一个表示狗的类并继承Animal类 class Dog extends Animal { run() { console.log(this.name + '在跑') } sayHello() { console.log('汪汪汪汪!') } } // 定义一个表示猫的类并继承Animal类 class Cat extends Animal {} const dog = new Dog('旺财', 5) const cat = new Cat('咪咪', 5) console.log(dog) dog.sayHello() dog.run() console.log(cat) cat.sayHello() })()
-
super 关键字
;(function () { class Animal { name: string constructor(name: string) { this.name = name } sayHello() { console.log('动物在叫') } } class Dog extends Animal { age: number constructor(name: string, age: number) { // 如果在子类中写了构造函数,在子类的构造函数中必须对父类的构造函数进行调用 super(name) // 调用父类的构造函数 this.age = age } sayHello() { // 在类的方法中,super就表示当前类的父类 super.sayHello() } } const dog = new Dog('旺财', 3) dog.sayHello() })()
-
抽象类
;(function () { /** * 以abstract开头的类是抽象类 * 抽象类和其他类区别不大,只是不能用来创建对象 * 抽象类就是专门用来继承的类 * * 抽象类中可以添加抽象方法 */ abstract class Animal { name: string constructor(name: string) { this.name = name } /** * 定义一个抽象方法 * 抽象方法使用abstract开头,没有方法体 * 抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写 */ abstract sayHello(): void } class Dog extends Animal { sayHello() { console.log('汪汪汪汪!') } } class Cat extends Animal { sayHello() { console.log('喵喵喵!') } } const dog = new Dog('旺财') dog.sayHello() })()
-
接口
;(function () { // 描述一个对象的类型 type myType = { name: string age: number } // const obj: myType = { // name: 'ABing', // age: 20, // } /** * 接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法,同时接口可以当成类型声明去使用 */ interface myInterface { name: string age: number } interface myInterface { gender: string } const obj: myInterface = { name: 'ABing', age: 20, gender: 'male', } /** * 接口可以在定义类的时候去限制类的结构 * 接口中所有的属性都不能有实际的值 * 接口只定义对象的结构,而不考虑实际值 * 在接口中所有的方法都是 抽象方法 */ interface myInter { name: string sayHello(): void } /** * 定义类时,可以使类去实现一个接口 * 实现接口就是使类满足接口的要求 */ class MyClass implements myInter { name: string constructor(name: string) { this.name = name } sayHello() { console.log('Hello') } } const myClass = new MyClass('ABing') myClass.sayHello() })()
-
泛型
/** * 在定义函数或类时,如果遇到类型不明确时就可以使用泛型 */ function fn<T>(a: T): T { return a } // 可以直接调用具有泛型的函数 // 不指定泛型,TS可以自动对类型进行推断 fn(10) // 指定泛型 fn<string>('hello') // 泛型可以同时指定多个 function fn2<T, K>(a: T, b: K): T { return a } fn2<number, string>(123, 'hello') interface Inter { length: number } // 表示泛型T必须是Inter实现类(子类) function fn3<T extends Inter>(a: T): number { return a.length } fn3('222') class MyClass<T> { name: T constructor(name: T) { this.name = name } } const mc = new MyClass<string>('ABing')