目录
1、TypeScript简介
1、TypeScript 由微软开发,是基于 JavaScript 的一个扩展语言。
2、TypeScript 包含了JavaScript的所有内容,即TypeScript 是JavaScript 的超集。
3、TypeScript 增加了:静态类型检查、接口、泛型等很多现代开发特性,因此更适合大型项目的开发。
4、TypeScript 需要编译为 JavaScript ,然后交给浏览器或其他 JavaScript 运行环境执行。
2、为何需要TypeScript
2.1 今非昔比的JavaScript
- JavaScript 当年诞生时的定位是浏览器脚本语言,用于在网页中嵌入一些简单的逻辑,而且代码量很少。
- 随着时间的推移,JavaScript 变得越来越流行,如今的 JavaScript 已经可以全栈编程了
- 现如今的 JavaScript 应用场景比当年丰富的多,代码量也比当年大很多,随便一个 JavaScript 项目的代码量,可以轻松的达到几万行,甚至十几万行!
- 然而 JavaScript 当年“出生简陋”,没考虑到如今的应用场景和代码量,逐渐的就出现了很多困扰。
2.2 JavaScript中的困扰
- 不清不楚的数据类型
- 有漏洞的逻辑
- 访问不存在的属性
- 低级的拼写错误
2.3 静态类型检查
- 在代码运行前进行检查,发现代码的错误或不合理之处,减小运行时异常的出现的几率,此种检查叫『静态类型检查』,TypeScript 和核心就是『静态类型检查』,简言之就是把运行时的错误前置。
- 同样的功能,TypeScript 的代码量要大于JavaScript,但由于 TypeScript 的代码结构更加清晰,在后期代码的维护中 TypeScript 却远胜于JavaScript.
3、编译TypeScript
浏览器不能直接运行 TypeScript 代码,需要编译为 JavaScript 再交由浏览器解析器执行。
3.1 命令行编译
1、创建一个index.ts文件:例如:
2、全局安装TypeScript
npm i typescript -g
3、使用命令将ts文件编译为js文件
tsc index.ts
3.2 自动化编译
1、创建TypeScript编译控制文件
tsc --init
- 工程中会生成一个tsconfig.json的配置文件,其中包含着很多编译时的配置
- 观察发现,默认编译的JS版本是ES7,可以手动调整为其他版本
2、监视目录中的ts文件的变化
tsc --watch
3、优化:当编译出错时不生成js文件
tsc --noEmitOnError --watch
备注:也可以直接修改tsconfig.json中的noEmitOnError配置
4、类型声明
let a: string let b: number let c: boolean let d: 'xxx' a = '123' b = 123 c = true d = 'xxx' function count(x: number, y: number): number { return x + y } console.log(count(1, 2)); // count(1) // count(1, 2, 3)
5、类型推断
6、类型总览
6.1 JavaScript中的数据类型
- string
- number
- boolean
- null
- undefined
- bigint
- symbol
- object
备注:其中 object 包含:Arrary、Function、Date、Error等......
6.2 TypeScript中的数据类型
1、上述所有JavaScript类型
2、六个新类型
- any
- unknown
- never
- void
- tuple
- enum
3、两个用于自定义类型的方式
- type
- interface
6.3 注意点
在 Javascript 中的这些内置构造函数:Number、String、Boolean,它们用于创建对应的包装对象,在日常开发时很少使用,在 TypeScript 中也是同理,所以在 Typescript 中进行类型声明时,通常都是用小写的 number、string、boolean
let str1: string // TS官方推荐的写法 str1 = '123' // str1 = new String() 报错:不能将类型“String”分配给类型“string”。“string”是基元,但“String”是包装器对象。如可能首选使用“string”。 let str2: String str2 = '123' str2 = new String('123')
1、原始类型VS包装对象
- 原始类型:如number、string、boolean,在JavaScript中是简单数据类型,它们在内存中占用空间少,处理速度快
- 包装对象:如Number对象、String对象、Boolean对象,是复杂类型,在内存中占用更多空间,在日常开发时很少由开发人员自己创建包装对象
2、自动装箱:JavaScript在必要时会自动将原始类型包装成对象,以便调用方法或访问属性
// 原始类型字符串 let str = 'hello' // 当访问str.length时 JavaScript引擎做了以下工作 let size = (function () { // 1、自动装箱:创建一个临时的String对象包装原始字符串 let tempStringObject = new String(str) // 2、访问String对象的length属性 let lengthValue = tempStringObject.length // 3、销毁临时对象,返回长度值 // JavaScript引擎自动处理对象销毁 开发者无感 return lengthValue })() console.log(size); // 5
7、常用类型
7.1 any
any的含义是:任意类型,一旦将变量类型限制为any,那就意味着放弃了对该变量的类型检查
let str: any str = '123' str = 123 str = true
注意点:any类型的变量可以赋值给任意类型的变量
let a: any a = '123' a = 123 a = true let b: string b = a // 无警告
7.2 unknown
unknow的含义是:未知类型,可以理解为一个类型安全的any,适用于:不确定数据的具体类型
// 设置a的类型为unknow let a: unknown a = 99 a = '123' a = true let b: string // b = a 报错:不能将类型“unknown”分配给类型“string”。
unknow 会强制开发者在使用之前进行类型检查,从而提供更强的类型安全性
// 设置a的类型为unknow let a: unknown a = 99 a = '123' a = true let b: string // 第一种 if (typeof a === 'string') { b = a } // 第二种(断言) b = a as string // 第三种(断言) b = <string>a
读取any类型数据的任何属性都不会报错,二unknow正好与之相反
let str1: string str1 = '123' str1.toUpperCase() // 无警告 let str2: any str2 = '123' str2.toUpperCase() // 无警告 let str3: unknown str3 = '123' str3.toUpperCase() // 警告:“str3”的类型为“未知”。 // 使用断言强制指定str3的类型为string (str3 as string).toUpperCase() // 无警告
7.3 never
never的含义是:任何值都不是,简而言之就是不能有值,undefined、null、''、0都不行!
1、几乎不用never去直接限制变量,因为没有意义,例如
let a: never // 一下对a的所有赋值都会有警告 a = 1 a = true a = '123'
2、never一般是TypeScript主动推断出来的,例如
let a: string a = '123' if (typeof a === 'string') { console.log(a.toUpperCase()); } else { console.log(a); // TypeScript会推断出此处的a是nerve 因为没有任何一个值符合此处的逻辑 }
3、never也可以用于限制函数的返回值
// nerve一般作用域函数。表示该函数不能正常运行完毕。或者一直在运行 function a(): never { throw new Error('程序报错') }
7.4 void
1、void通常用于函数返回值声明,含义:函数不返回任何值,调用者也不应依赖其返回值进行任何操作
function abc(str: string): void { console.log(str); // return 13 // 不能将类型“number”分配给类型“void”。 } abc('hh')
注意:编码者没有编写return去指定函数的返回值,所以abc函数是没有显示返回值的,但会有一个隐式返回值,就是undefined;即:虽然函数返回类型为void,但也是可以接受undefined的,简单记:undefined是void可以接受的一种‘空’
2、以下写法均符合规范
function abc(str: string): void { console.log(str); } function abc1(str: string): void { console.log(str); return } function abc2(str: string): void { console.log(str); return undefined }
3、返回值类型为void的函数,调用者不应依赖其返回值进行任何操作
function abc(str: string): void { console.log(str); } const result = abc('hhh') if (result) { // 无法测试 "void" 类型的表达式的真实性。 } function abc1(str: string): undefined { console.log(str); return undefined } const result1 = abc1('hhh') if (result1) { // 无警告 }
4、理解void和undefined
- void 是一个广泛的概念,用来表达“空”,而 undefined 则是这种“空”的具体实现之一。
- 因此可以说 undefined 是 void 能接受的“空”状态的一种具体形式。
- 换句话说: void 包含 undefined ,但 void 表达的语义超越了单纯的 undefined ,它是一种意图上的约定,而不仅仅是特定值的限制。
5、总结:若函数返回类型为 void ,那么:
- 从语法上讲:函数是可以返回 undefined 的,至于显示返回,还是隐式返回,这无所谓!
- 从语义上讲:函数调用者不应关心函数返回的值,也不应依赖返回值进行任何操作!即使返回了 undefined 值
7.5 object
7.5.1 object小写
object(小写)的含义是:所有非原始类型,可存储:对象、函数、数组等,由于限制的范围比较宽,在实际开发中使用的相对较少
let a: object // a能存储的类型是【非原始类型】 let b: Object a = {} a = [] a = function () { } a = new String('123') class Person { } a = new Person() a = 1 // 警告:不能将类型“number”分配给类型“object”。 a = true// 警告:不能将类型“boolean”分配给类型“object” a = '你好'// 警告:不能将类型“string”分配给类型“object”。 a = null// 警告:不能将类型“null”分配给类型“object” a = undefined// 警告:不能将类型“undefined”分配给类型“object”
7.5.2 Object大写
- 官方描述:所有可以调用Object方法的类型
- 简单记忆:除了undefined和null的任何值都能存
- 由于限制的范围太大了,实际开发中使用频率极低
let a: object // a能存储的类型是【非原始类型】 let b: Object // b能存储的类型是可以调用到Object方法的类型 b = {} b = [] b = function () { } b = new String('123') class Person { } b = new Person() b = 1 b = true b = '你好' b = null // 警告:不能将类型“null”分配给类型“object” b = undefined // 警告:不能将类型“undefined”分配给类型“object” */
7.5.3 声明对象类型
1、实际开发中:限制一般对象,通常使用一下形式
// 写法1 let person1: { name: string age?: number } // 写法2 let person2: { name: string, age?: number } // 写法3 let person3: { name: string; age?: number } person1 = { name: '张三', age: 99 } person1 = { name: '张三' }
2、索引签名:允许定义对象可以具有任意数量的属性,这些属性的键和类型是可变的,常用于:描述类型不确定的属性(具有动态属性的对象)
let person1: { name: string age?: number // 索引签名,可以添加任意数量的属性 [key: string]: any } person1 = { name: '张三', age: 99, height: 100, city: '北京' }
7.5.3 声明函数类型
let count: (x: number, y: number) => number count = function (x, y) { return x + y }
备注:
- TypeScript中的=>在函数类型声明时表示函数类型,描述其参数类型和返回类型
- JavaScript中的=>是一种定义函数的语法,是具体的函数实现
- 函数类型声明还可以使用:接口、自定义类型等方式
7.5.3 声明数组类型
let arr1: string[] let arr2: Array<number> // arr2是数组,并且里面的每一个元素都是number类型 arr1 = ['2', '1'] arr2 = [1, 2]
7.6 tuple
元组(Tuple)是一种特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的且可以不同,元组用于精确描述一组值的类型,?表示可选元素
// 第一个类型必须是string类型 第二个元素必须是number类型 let arr1: [string, number] // 第一个元素必须是number类型,第二个元素是可选的,如果存在,必须是boolean类型 let arr2: [number, boolean?] // 第一个元素必须是number类型,后面的元素可以是任意数量的string类型 let arr3: [number, ...string[]] arr1 = ['123', 1] // 警告:不能将类型“[string]”分配给类型“[string, number]”。源具有 1 个元素,但目标需要 2 个。 arr1 = ['123'] arr2 = [1] arr2 = [1, true] arr3 = [1, '1', '2'] // 警告:不能将类型“[number, string, string, number]”分配给类型“[number, ...string[]]”。源中位置 1 到 3 的类型与目标中位置 1 的类型不兼容。能将类型“number”分配给类型“string”。 arr3 = [1, '1', '2', 3]
7.7 enum
枚举(enum)可以定义一组命名常量,它能增强代码的可读性,也让代码更好维护
如下代码的功能是:根据调用walk时传入的不同参数,执行不同的逻辑,存在的问题是调用walk时传参时没有任何提示,编码者很容易写错字符串内容;并且用于判断逻辑的up、down、left、right是连续且相关的一组值,那此时就特别适合使用枚举(enum)
function walk(str: string) { if (str === 'up') { console.log('上'); } else if (str === 'down') { console.log('下'); } else if (str === 'left') { console.log('左'); } else if (str === 'right') { console.log('右'); }else{ console.log('其他情况'); } } walk('up') walk('down') walk('left') walk('right')
7.7.1 数字枚举
数字枚举是一种常见的枚举类型,其成员的值会自动递增,且数字枚举还具备反向映射的特点,可以通过值来获取对应的枚举成员名称
enum Direction { up, down, left, right } Direction.up = 99 // 警告:无法为“up”赋值,因为它是只读属性。 console.log(Direction.up); // 0 console.log(Direction.down); // 1 console.log(Direction.left); // 2 console.log(Direction.right); // 3 // 还具有反向映射的特点 console.log(Direction[0]); // up console.log(Direction[1]); // down console.log(Direction[2]); // left console.log(Direction[3]); // right function walk(str: Direction) { if (str === Direction.up) { console.log('上'); } else if (str === Direction.down) { console.log('下'); } else if (str === Direction.left) { console.log('左'); } else if (str === Direction.right) { console.log('右'); } else { console.log('其他情况'); } } walk(Direction.up) walk(Direction.down) walk(Direction.left) walk(Direction.right)
7.7.2 字符串枚举
会丢失反向映射
enum Direction { up = 'up', down = 'down', left = 'left', right = 'right' } let dir: Direction = Direction.up console.log(dir); // 输出:up
7.7.3 常量枚举
官方描述:常量枚举是一种特殊枚举类型,它使用const关键字定义,在编译时会被内联,避免生成一些额外的代码
何为编译时内联?
所谓“内联”其实就是 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象。这可以减少生成的 JavaScript 代码量,并提高运行时性能。使用普通枚举的TypeScript代码如下,编译后生成的JavaScript代码量较大
enum Direction { up, down, left, right } let x = Direction.up console.log(x); // 以下是ts转换为js的代码 "use strict"; var Direction; (function (Direction) { Direction[Direction["up"] = 0] = "up"; Direction[Direction["down"] = 1] = "down"; Direction[Direction["left"] = 2] = "left"; Direction[Direction["right"] = 3] = "right"; })(Direction || (Direction = {})); let x = Direction.up; console.log(x);
使用常量枚举的TypeScript代码如下,编译后生成的JavaScript代码量较小
const enum Direction { up, down, left, right } let x = Direction.up console.log(x); // 以下是ts转换为js的代码 "use strict"; let x = 0 /* Direction.up */; console.log(x);
7.8 type
type可以为任意类型创建别名,让代码更简介、可读性更强,同时能更方便的进行类型复用和扩展
1、基本用法
类型别名使用type关键字定义,type后跟类型名称,例如下面代码中num是类型别名
type num = number let a:num a = 100
2、联合类型
联合类型是一种高级类型,它表示一个值可以是几种不同类型之一
type Status = number | string type Gender = '男' | '女' function logStatus(data: Status): void { console.log(data); } logStatus('404') logStatus(404) function logGender(data: Gender): void { console.log(data); } logGender('女')
3、交叉类型
交叉类型,允许多个类型合并为一个类型,合并后的类型将拥有所有被合并类型的成员,交叉类型通常用于对象类型
type Area = { height: number width: number } type Address = { num: number cell: number room: string } type House = Area & Address const house: House = { height: 100, width: 100, num: 1, cell: 1, room: '1' }
7.9 一个特殊情况
1、在函数定义时,限制函数返回值的void,那么函数的返回值就必须是空
function demo(): void { // 返回undefined合法 return undefined // 以下均不合法 // return 100 // return null // return [] // return false }
2、使用类型声明限制函数返回值为void时,TypeScript并不会严格要求函数返回为空
type Fn = () => void const f1: Fn = function () { return 123 } const f2: Fn = () => 123
3、原因
为了确保如下代码成立,因为Array.prototype.push的返回一个数字,而Array.prototype.forEach方法期望其回调的返回类型是void
const arr = [1, 3, 4] const a = [0] arr.forEach(item => a.push(item))
7.10 属性修饰符
修饰符 含义 具体规则 public 公开的 可以被:类内部、子类、类外部访问 protected 受保护的 可以被:类内部、子类访问 private 私有的 可以被:类内部访问 readonly 只读属性 属性无法修改
public修饰符
class Person { public name: string public age: number constructor(name: string, age: number) { this.name = name this.age = age } public speak() { console.log(this.name, this.age); } } class Student extends Person { a() { console.log(this.name); } } const p1 = new Person('张三', 99) console.log(p1.name);
属性的简写形式
class Person { // public name: string // public age: number constructor(public name: string, public age: number) { } }
protected修饰符
class Person { constructor(protected name: string, protected age: number) { } protected getDetails() { return this.name } introduce() { console.log(this.getDetails()); } } class Student extends Person { a() { // 子类可以用 console.log(this.name); console.log(this.introduce()); } } const p1 = new Person('张三', 99) console.log(p1.name); // 属性“name”受保护,只能在类“Person”及其子类中访问 p1.introduce()
private修饰符
class Person { constructor(private name: string) { } getName() { console.log(this.name); } } class Student extends Person { a() { // console.log(this.name); // 警告:属性“name”为私有属性,只能在类“Person”中访问 } } const p1 = new Person('张三') // console.log(p1.name); // 警告:属性“name”为私有属性,只能在类“Person”中访问。
readonly修饰符
class Person { constructor(readonly name: string) { } getName() { console.log(this.name); } } const p1 = new Person('张三') // p1.name = 'ss' // 警告:无法为“name”赋值,因为它是只读属性。
7.11 抽象类
- 概述:抽象类是一种无法被实例化(new)的类,专门用来定义的结构和行为,类中可以写抽象方法,也可以写具体实现,抽象类主要用来为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法
- 简记:抽象类不能实例化,其意义是可以被继承,抽象类里面可以有普通方法,也可以有抽象方法
通过以下场景,理解抽象类:
abstract class Package { constructor(public weight: number) { } // 抽象方法 abstract calculate(): number // 具体方法 printPackage() { console.log('我是具体方法', this.calculate()); } } class standardPackage extends Package { constructor(weight: number, public unitPrice: number) { super(weight) } calculate(): number { return this.weight * this.unitPrice } } const s1 = new standardPackage(10, 5) s1.printPackage()
总结:何时使用抽象类
- 定义通用接口:为一组相关的类定义通用的行为(方法或属性)时。
- 提供基础实现:在抽象类中提供某些方法或为其提供基础实现。这样派生类就可以继承这些实现。
- 确保关键实现:强制派生类实现的一些关键行为。
- 共享代码和逻辑:当多个类需要共享这部分代码时。抽象类可以避免代码重复。
7.12 interface(接口)
- 概述:interface是一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意interface只能定义格式,不能包含任何实现
- 定义类结构
interface PersonInterface { name: string age: number speak(n: number): void } class Person implements PersonInterface { constructor(public name: string, public age: number) { } speak(n: number): void { console.log(n, this.name, this.age); } } const p1 = new Person('tom', 18) p1.speak(1)
- 定义对象结构
interface PersonInterface { name: string readonly age: number // 只读属性 speak(n: number): void } const Person: PersonInterface = { name: '123', age: 11, speak(n) { console.log(n); } }
- 定义函数结构
interface CountInterface { (a: number, b: number): number } const count: CountInterface = (x, y) => { return x + y }
- 接口之间的继承
interface PersonInterface { name: string, age: number } interface StudentInterface extends PersonInterface { grade: string } const student: StudentInterface = { name: '123', age: 123, grade: '123' }
- 接口自动合并(可重复定义)
interface PersonInterface { name: string, age: number } interface PersonInterface { gender: string } const person: PersonInterface = { name: '张安', age: 99, gender: '99' }
总结:何时使用接口?
- 定义对象的格式:描述数据模型、API响应格式、配置对象.......等等,是开发中用的最多的场景。
- 类的契约:规定一个类需要实现哪些属性和方法。
- 自动合并:一般用于扩展第三方库的类型,这是特性在大型项目中可能会用到
7.13 一些相似概念的区别
7.13.1 interface和type的区别
- 相同点:interface和type都可以用于定义对象结构,两者在许多场景中是可以互换的
- 不同点:
1、interface:更专注于定义对象和类的结构,支持继承、合并
2、type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并7.13.1 interface和抽象类的区别
- 相同点:都用于定义一个类的格式(应该遵循的契约)
- 不同点:
1、接口:只能描述结构,不能有任何实现代码,一个类可以实现多个接口
2、抽象类:既可以包含抽象方法,也可以包含具体方法,一个类只能继承一个抽象类
8、泛型
概述:泛型允许我们在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时,才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性
8.1 泛型函数
function a<T>(data: T) { console.log(data); } a<number>(100) a<string>('100')
8.2 泛型可以有多个
function a<T, U>(data1: T, data2: U): T | U { return data1 || data2 } a<number, boolean>(100, true) a<string, boolean>('100', true)
8.3 泛型接口
interface PersonInterface<T> { name: string age: number extraInfo: T } type JobInfo = { title: string, company: string } const p: PersonInterface<JobInfo> = { name: '张三', age: 18, extraInfo: { title: '123', company: '123' } }
8.4 泛型类
class Person<T> { constructor(public name: string, public age: string, public extraInfo: T) { } speak() { console.log(this.name); console.log(this.age); console.log(this.extraInfo); } } const p1 = new Person<number>('tom', '30', 30)
9、类型声明文件
概述:类型声明文件TypeScript中的一种特殊文件,通常以.d.ts作为扩展名,它的主要作用是为现有的JavaScript代码提供类型信息,是的TypeScript能够在使用这些JavaScript库或模块时进行类型检查和提示