1、TypeScript介绍
1.1 TypeScript 是什么:Type + JavaScript
TypeScript(简称:TS)是 JavaScript 的超集(JS 有的 TS 都有)。
在 JS 的基础之上,为 JS 添加了 类型支持
TypeScript 是微软开发的开源编程语言,可以在任何运行 JavaScript 的地方运行
1.2 TypeScript为什么要添加类型支持?
背景:JS的类型系统存在 先天缺陷 ,JS代码中绝大部分错误都是类型错误(Uncaught TypeError)
原因:增加了找Bug,改Bug的时间,严重影响开发效率。
TypeScript属于静态类型的编程语言,静态编程语言在编译器做类型检查。
JavaScript属于动态类型的编程语言,动态编程语言在执行器做类型检查。
代码是先编译再执行的
TS 可以 提前到在编写代码的同时 就发现代码中的错误, 减少找 Bug、改 Bug 时间
1、3 TypeScript 相比 JS 的优势
更早(写代码的同时)发现错误,减少找Bug、改Bug的时间,提高开发效率
程序中的任何位置的代码都有代码提示,增强了开发体验
支持ECMAScript语法,有强大的类型系统,会自动进行TS类型推断
2、TypeScript体验
2.1 安装编译TS的工具包:
因为node.js / 浏览器只认识JS代码,不认识TS代码,所以需要将TS代码转换为JS代码才能运行。
安装命令: npm install -g typescript
这是typescript包,用来编译ts代码,提供了tsc命令,可以将 TS 代码转化为 JS代码
2.2 编译并运行TS代码:
①:创建hello.ts文件
②:将TS编译为JS:在终端中输入命令,tsc hello.ts (此时,目录中会编译生成一个hello.js文件)
③:执行JS代码:在终端输入命令 node hello.js 回车输出内容
注意:由 TS 编译生成的 JS 文件 ,代码中就 没有类型信息 了。
2.3 简化运行TS的步骤:
①:使用 ts-node 包,直接在node.js中执行TS代码
安装命令:npm install -g ts-node (ts-node 包提供了 ts-node 命令)
使用:ts-node hello.ts 回车
解释:ts-node 命令在内部偷偷将 TS 转化为了 JS,再运行JS代码
3、TyepScript常用类型
3.1 类型注解:
let age: number = 18
// : number 就是类型注解
// 约定了什么类型,就只能给变量赋值该类型的值,否则,就会报错
3.2 常用基础类型:
分为 2 类:JS已有类型、TS新增类型
①:JS已有类型:
原始类型:number / string / boolean / null / undefined / symbol
对象类型:object (包括:数组、对象、函数)
②:TS新增类型:
联合类型、自定义类型(也叫类型别名)、接口、元组、字面量类型、枚举、void、any等
3.3 原始类型:
let age: number = 18
let myName: string = '广州南方学院'
let isLoading: boolean = false
let a: null = null
let b: undefined = undefined
let s: symbol = Symbol()
3.4 数组类型:
// 推荐使用
let numbers: number[] = [1, 3, 5, 6]
// 不推荐使用
let numbers1: Array<number> = [1, 3, 5, 6]
let b: boolean[] = [true, false]
// 联合类型 |
// 添加小括号,表示:首先是数组,然后这个数组中能够出现 number 或 string 类型的元素
let arr: (number | string)[] = [1, 3, 5, 'a', 'b']
// 不添加小括号,表示:arr1 既可以是number 类型,又可以是 string[]
let arr1: number | string[] = ['a', 'b']
let arr2: number | string[] = 123
3.5 类型别名:
使用 type 关键字来创建类型别名。
类型别名 (自定义类型):为任意类型起别名。
使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名, 简化该类型的使用 。
// 类型别名
type CustomArray = (number | string)[]
let arr: CustomArray = ['l', 5, 'y', 2, 't', 0]
let arr1: CustomArray = ['k', 5, 'e', 2, 'a', 0, 'i']
3.6 函数类型:
函数的类型实际上指的是:函数参数和返回值类型
/**
*1、单独指定参数、返回值类型
*/
// 普通函数
function add(num1: number, num2: number): number {
return num1 + num2
}
// 调用
console.log(add(1, 2)) // 3
// 箭头函数
const addNum = (num1: number, num2: number): number => {
return num1 + num2
}
// 调用
console.log(addNum(2, 4)); // 6
/**
* 2、同时指定参数、返回值的类型
*/
const sub**: (num3: number, num4: number) => number** = (num3, num4) => {
return (num3 - num4)
}
console.log(add(6, 3));
如果函数没有返回值,那么,函数返回值类型为:void
function greet(name: string): void {
console.log('Hello' + name)
}
// 调用
greet('lyt')
可选参数::在可传可不传的参数名称后面添加 ?(问号)
注意: 可选参数只能出现在参数列表的最后 ,也就是说可选参数后面不能再出现必选参数
function mySlice(start?: number, end?: number): void {
console.log('起始索引:', start, '结束索引:', end);
}
mySlice()
mySlice(1)
mySlice(2, 3)
必选参数不能位于可选参数后
// 错误演示
function mySlice(start?: number, end: number): void {
console.log('起始索引:', start, '结束索引:', end);
}
3.7 对象类型:
TS 中 对象的类型 就是在 描述对象的结构 (有什么类型的属性和方法)
/* let person: {name: string;age: number;sayHi(): void;greet(name: string): void} = {
name: 'John',
age: 18,
sayHi() { },
greet(name) { }
}
*/
let person: {
name: string
age: number
// sayHi(): void
sayHi: () => void
greet(name: string): void
} = {
name: 'John',
age: 18,
sayHi() { },
greet(name) { }
}
对象类型的可选属性
// 对象类型的可选属性
function myAxios(config: { url: string; method?: string }) {
console.log(config);
}
myAxios({
url: "http://www.baidu.com"
})
3.8 接口:直接描述对象的结构,比如:人
当一个对象类型被多次使用时,一般会使用 接口 ( interface )来描述对象的类型,达到 复用 的目的
interface Person {
name: string
age: number
sayHi(): void
}
let person: Person = {
name: 'xwj',
age: 18,
sayHi() { }
}
let person1: Person = {
name: 'lyt',
age: 18,
sayHi() { }
}
interface(接口)和 type(类型别名)的对比:
相同点:都可以给对象指定类型
不同点:
接口:只能为对象指定类型
类型别名:可以为任意类型指定别名
使用 extends (继承)关键字实现了接口 Point3D 继承 Point2D
3.9 元组:元组类型可以确切的标记出有多少个元素,以及每个元素的类型
元组类型是另一种类型的数组,它 确切地知道包含多少个元素,以及特定索引对应的类型
// let position: number[] = [12,23,6,3] // 不严谨
// 使用元组
let position: [number, number] = [39, 52]
3.10 类型推论:在 TS 中,某些没有明确指出类型的地方,TS 的类型推论机制会帮助提供类型
3.11 类型断言:有时候你比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型。
使用 as 关键字实现类型断言。
3.12 字面量类型:
-
str1 是一个变量(let),它的值可以是任意字符串,所以类型为:string。
-
str2 是一个常量(const),它的值不能变化只能是 'Hello TS',所以,它的类型为:'Hello TS'。
注意:此处的 'Hello TS',就是一个字面量类型。也就是说某个特定的字符串也可以作为 TS 中的类型。
除字符串外,任意的 JS 字面量(比如,对象、数字等)都可以作为类型使用
let str1 = 'Hello TS'
const str2: 'Hello TS' = 'Hello TS'
let age: 18 = 18
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') { }
// 调用
changeDirection('up')
字面量类型配合联合类型一起使用
参数 direction 的值只能是 up/down/left/right 中的任意一个
3.13 枚举:枚举成员是有值的,默认为:从 0 开始自增的数值。
枚举的功能类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值。
枚举:定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
// 枚举 表示一组明确的可选值。
enum Direction {
Up,
Down,
Left,
Right
}
// 使用枚举 定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个。
function changeDirection(direction: Direction) {
console.log(direction);
}
// 调用函数 并访问枚举里面的成员
changeDirection(Direction.Up)
解释:
使用 enum 关键字定义枚举。
约定枚举名称、枚举中的值以大写字母开头。
枚举中的多个值之间通过 ,(逗号)分隔。
定义好枚举后,直接使用枚举名称作为类型注解。
数字枚举:
// 枚举 枚举成员的值默认是从 0 开始
/* enum Direction {
Up,
Down,
Left,
Right
} */
// 成员设置初始值
// 第一种
/* enum Direction {
Up = 10,
Down,
Left,
Right
} */
// 第二种
enum Direction {
Up = 5,
Down = 2,
Left = 0,
Right = 1
}
// 类似于 JS 中的对象,直接通过点(.)语法访问枚举的成员
// 使用枚举
function changeDirection(direction: Direction) {
console.log(direction);
}
// 调用函数 并访问枚举里面的成员
changeDirection(Direction.Up)
字符串枚举:
// 字符串枚举 字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值。
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
// 使用枚举
function changeDirection(direction: Direction) {
console.log(direction);
}
// 调用函数
changeDirection(Direction.Right)
// console.log(Direction);
3.14 any类型:
// 当值为any时,可以对该值进行任意操作,并且不会有代码提示。
let obj: any = { x: 0 }
obj.bar = 100
obj()
const n: number = obj
// 以上操作都不会有任何类型错误提示,即使可能存在错误
let a
a = 1
a = ''
a()
// 可以随意传内容
function add(num1, num2) { }
add(1, 2)
add('1', 2)
add(false, 1)
3.15 typeof:
-
使用 typeof 操作符来获取变量 p 的类型,结果与第一种(对象字面量形式的类型)相同。
-
typeof 出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于 JS 代码)。
-
注意:typeof 只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)
console.log(typeof 'Hello TS'); // 打印 string
// typeof 查询变量或属性的数据类型
let p = { x: 1, y: 2 }
function formatPoint(point: typeof p) { }
// function formatPoint(point: { x: number, y: number }) { }
formatPoint({ x: 3, y: 5 })
//----
let num: typeof p.x
4、TypeScript高级类型
TS高级类型有 class、类型兼容性、交叉类型、泛型和keyof、索引签名类型和索引查询类型、映射类型
①:class类:
// class 基本使用
class Person {
age: number;
gender = '男'
// gender: string = '男'
}
const p = new Person()
p.age
p.gender
// 解释:
// 1、根据TS中的类型推论,可以知道Person类的实例对象p的类型是Person
// 2、TS中的class,不仅提供了class的语法功能,也作为一种类型存在
构造函数:
// 构造函数不能有返回值类型
class Person {
age: number
gender: string
// 构造函数
constructor(age: number, gender: string) {
this.age = age
this.gender = gender
}
}
// 实例化
const p = new Person(18, '男')
console.log(p.age, p.gender);
实例方法:
class Point {
x = 1
y = 2
// 缩放
scale(n: number) {
this.x *= n
this.y *= n
}
}
// 实例化
const p = new Point()
p.scale(10) // 放大10倍
console.log(p.x, p.y); // 10 20
继承:
class Animal {
move() {
console.log('走几步');
}
}
// 继承之后,子类就会拥有父类所有的属性和方法
// 子类Dog 继承 父类Animal
class Dog extends Animal {
name = '二哈'
bark() {
console.log('狗叫');
}
}
// 实例化子类
const d = new Dog()
d.move()
d.bark()
console.log(d.name);
接口实现:
interface Singable {
name: string
sing(): void
}
// 接口实现
class Person implements Singable {
name = 'xwj'
sing() {
console.log('&可爱');
}
}
const p = new Person()
p.sing()
console.log(p.sing, p.name);
// 通过 implements 关键字让 class 实现接口
// Person 类实现接口 Singable 意味着,Person类中必须提供Singable接口中指定的所有的属性和方法
成员可见性:public(公有的) protected(受保护的) private(私有的)
public(公有的)
// 成员可见性:可以使用TS来控制class的方法或属性对于class外的代码是可见的
// 可见性修饰符:public(公有的) protected(受保护的) private(私有的)
// 父类
class Animal {
public move() {
console.log('走两步')
}
}
const a = new Animal()
a.move()
// 子类
class Dog extends Animal {
bark() {
console.log('狗叫');
}
}
// 实例化子类
const d = new Dog()
d.bark()
d.move()
成员可见性:public(公有的) protected(受保护的) private(私有的)
protected(受保护的)
// protected:表示受保护的,仅对其所在类和子类中(非实例对象)可见
// 父类
class Animal {
protected move() { // 不能在实例对象中访问
console.log('父类动物');
}
run() {
this.move()
console.log('走两步');
}
}
const a = new Animal()
// 子类 继承父类
class Dog extends Animal {
bark() {
this.move()
console.log('狗叫');
this.move()
}
}
// 实例化对象 不能子这里访问
const d = new Dog()
成员可见性:public(公有的) protected(受保护的) private(私有的)
private(私有的)
// private:表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的
class Animal {
// 在类或者属性前面添加 private 关键字,来修饰该属性或方法
private run() {
console.log('Animal 内部辅助函数');
}
// 受保护的
protected move() {
this.run()
console.log('走两步');
}
// 公开的 public可省略
run() {
this.run()
this.move()
console.log('跑起来');
}
}
// 实例化父类
const a = new Animal()
// 子类
class Dog extends Animal {
bark() {
console.log('狗叫');
}
}
// 实例化子类
const d = new Dog()
d.bark()
d.run()
readonly 修饰符:
// readonly 表示只读,用来防止在构造函数之外对属性进行赋值
// class Person {
// 只读属性 可以用在类、接口和普通对象中
// readonly age: number = 18
// constructor(age: number) {
// this.age = age
// }
// // 错误演示
// // readonly setAge() { // 不能用来修饰方法,仅能用来修饰属性
// // // this.age = 20 // 报错
// // }
// }
// 接口或者 {} 表示的对象类型,也可以使用 readonly
class Person {
// 只读属性 可以用在类、接口和普通对象中
// 注意:只要是 readonly 来修饰的属性,必须手动提供明确的类名
readonly age: number = 18
constructor(age: number) {
this.age = age
}
}
// ----
// 接口
interface IPerson {
name: string
}
let obj: IPerson = {
name: 'xwj'
}
obj.name = '可爱' // 不是readonly 可以修饰属性值
// let obj: {
// readonly name: string
// }
// obj.name = '可爱' // 报错
②:类型兼容性:
// 演示类型兼容性:
// let arr = ['a','b','c']
// arr.forEach(item => {})
// arr.forEach((item,index) => {})
// arr.forEach((item,index,array) => {})
// 两种类型系统 1、 结构化类型系统 2、 标明类型系统
// TS采用的是结构化类型系统,也叫鸭子类型,类型关注的是值所具有的形状
// 也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型
// 两个类的兼容性演示
class Point {
x: number
y: number
}
class Point2D {
x: number
y: number
}
const p: Point = new Point2D()
准确:
// 注意:在结构化类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型,这种说法并不准确。
// 更准确的说法:对象对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给少的)
class Point {
x: number
y: number
}
class Point2D {
x: number
y: number
}
const p: Point = new Point2D()
class Point3D {
x: number
y: number
z: number
}
const p1: Point = new Point3D()
// 解释:1、Point3D的成员至少与Point相同,则Point兼容Point3D 2、所以,成员多的Point3D可以赋值给成员少的Point
接口:
// 除了class之外,TS中的其它类型也存在相互兼容的情况 ,包括:1、接口兼容性 2、函数兼容性
// 接口兼容性 类似于class,并且,class和interface之间也可以兼容
interface Point {
x: number
y: number
}
interface Point2D {
x: number
y: number
}
interface Point3D {
x: number
y: number
z: number
}
let p1: Point
let p2: Point2D
let p3: Point3D
// 正确演示
// p1 = p2 // 在赋值前使用了变量"p2"
// p2 = p3 // 在赋值前使用了变量"p3"
// 类和接口之间也是兼容的
class Point4D {
x: number
y: number
z: number
}
p2 = new Point4D()
函数参数个数:
// 在JS中省略用不到的函数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性
// 1、参数个:参数少的可以赋值给参数给参数多的
// 2、参数类型:相同位置的参数要相同或兼容
// 1 参数个数: 参数少的可以赋值给参数多的
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2
// f2 = f1
// 错误演示:
// f1 = f2
函数参数类型:
// 参数类型: 相同位置的参数类型要相同(原始类型)或兼容(对象类型)
// 2 参数类型: 相同位置的参数类型要相同或兼容
// 原始类型:
// type F1 = (a: number) => void
// type F2 = (a: number) => void
// let f1: F1
// let f2: F2
// f1 = f2
// f2 = f1
// --
// 对象类型
interface Point2D {
x: number
y: number
}
interface Point3D {
x: number
y: number
z: number
}
type F2 = (p: Point2D) => void // 相当于有 2 个参数
type F3 = (p: Point3D) => void // 相当于有 3 个参数
let f2: F2
let f3: F3
// f3 = f2
// f2 = f3
返回值类型:
// 返回值类型,只需要关注返回值类型本身即可
// 原始类型
type F5 = () => string
type F6 = () => string
// 声明变量
let f5: F5
let f6: F6
// 正确演示
// f6 = f5
// f5 = f6
// 对象类型
type F7 = () => {
name: string
}
type F8 = () => {
name: string
age: number
}
let f7: F7
let f8: F8
// f7 = f8
// 错误演示
// f8 = f7
③:交叉类型:
// 交叉类型( & ):功能类似于接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
interface Person {
name: string
}
interface Contact {
phone: string
}
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: 'lyt',
phone: '123...'
}
// 解释:使用交叉类型后,新的类型 PersonDetail就同时具备了 Person 和 Contact的所有属性类型
// 就相当于:
/* type PersonDetail = {
name: string
phone: string
} */
④:泛型和 keyof:
// 使用泛型来创建一个函数
function id<Type>(value: Type): Type {
return value
}
// 调用泛型函数
// 1、以 number 类型调用泛型函数
const num = id<number>(10)
// 2、以 string 类型调用泛型函数
const str = id<string>('lyt')
// 3、以boolean 类型调用泛型函数
const bol = id<boolean>(false)