# 一、基础知识
# 1、类
在 JavaScript 学习的时候,我们就提到了:为了让其他语言的开发者更快的适应 js 语法,ES6 中提供了类的写法:
构造函数方式
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function () {
console.log(this.name, this.age)
}
var obj = new Person('lencamo', 22)
obj.say()
class 类方式
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
say() {
console.log(this.name, this.age)
}
}
var obj = new Person('lencamo', 22)
obj.say()
现在我们在 typescript 中写一下:
class Person {
// 在ts中,要对成员属性进行声明
userName: string
userAge: number
// 或者定义一个初始化值
// userName: ''
// age = 0
constructor(name: string, age: number) {
this.userName = name
this.userAge = age
}
say() {
console.log(this.userName, this.userAge)
}
}
var obj = new Person('lencamo', 22)
obj.say()
参数属性 ✍(成员属性声明简写)
相当于有前提的语法糖
class Person {
// 在ts中,要对成员属性进行声明
constructor(public name: string,public age: number) {
this.name = name
this.age = age
}
say() {
console.log(this.name, this.name)
}
}
var obj = new Person('lencamo', 22)
obj.say()
更多的,学过 java 的同学应该对下面的修饰符内容并不陌生:
下面这些修饰符,像 java 一样,可以直接加在类方法和类属性前面
readonly | 只读的 |
public | 公开可访问的 |
protected | 当前类/子类可访问的 |
private | 当前类可访问的 |
下面就结合类的继承写一个案例:
class Person {
protected userName: string
constructor(name: string) {
this.userName = name
}
}
class Children extends Person {
// constructor(userName: string) {
// super(userName)
// }
run() {
return this.userName
}
}
var obj = new Children('lencamo')
console.log(obj.run())
console.log(obj.userName) // 报错
# 2、访问器
让私有属性能在外界访问
class Person {
private _name: string
private _age: number
constructor(name: string, age: number) {
this._name = name
this._age = age
}
// getter/setter:对属性的访问进行拦截操作
set age(newValue: number) {
if (newValue >= 0 && newValue < 200) {
this._age = newValue
}
}
get age() {
return this._age
}
}
const p = new Person('lencamo', 21)
p.age = -10
console.log(p.age)
# 3、抽象类(abstract)
// 抽象方法必须出现在抽象类中
// 并且抽象类是不能被实例化的
abstract class Db {
abstract connection(): void
abstract auth(): void
}
// 子类可以继承并实现抽象类
class Mysql extends Db {
connection() {
//
}
auth() {
//
}
}
感受应用场景 ✍
- 以前
class Rectangle {
constructor(public width: number, public height: number) {}
getArea() {
return this.width * this.height
}
}
class Circle {
constructor(public radius: number) {}
getArea() {
return this.radius ** 2 * Math.PI
}
}
// 通用函数(类型不明确)
function calcArea(shape: Rectangle | Circle) {
return shape.getArea()
}
var area1 = calcArea(new Rectangle(10, 20))
var area2 = calcArea(new Circle(5))
console.log(area1)
console.log(area2)
- 现在
abstract class Shape {
// 待实现方法
abstract getArea(): number
}
class Rectangle extends Shape {
constructor(public width: number, public height: number) {
super()
}
getArea() {
return this.width * this.height
}
}
class Circle extends Shape {
constructor(public radius: number) {
super()
}
getArea() {
return this.radius ** 2 * Math.PI
}
}
// 通用函数(鸭子类型)
function calcArea(shape: Shape) {
return shape.getArea()
}
var area1 = calcArea(new Rectangle(10, 20))
var area2 = calcArea(new Circle(5))
console.log(area1)
console.log(area2)
拓展:鸭子类型
Typescript 对应类型检测的时候使用的就是鸭子类型
描述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
特点:
只关心属性和行为,不关心具体是不是对应的类型
现象:
class Person {
constructor(public name: string, public age: number) {}
}
class Dog {
constructor(public name: string, public age: number) {}
running() {}
swing() {}
}
function printPerson(p: Person) {
console.log(p.name, p.age)
}
// 下面类型检测不会报错?
printPerson(new Person('lencamo', 20))
printPerson(new Dog('狗狗', 5))
printPerson({ name: 'haha', age: 3 })
const person: Person = new Dog('狗狗', 5)
# 4、接口实现(implements)
implements 的中文意思:实现,其作用是提前对 class 属性和方法进行约束
class 类实现某个接口
interface IPerson {
star: string
age: number
sing: () => void
}
interface IRun {
running: () => void
}
// 接口使用1:对象类型
const ldh: IPerson = {
star: 'ldh',
age: 38,
sing: function () {
//
}
}
// 接口使用2:接口类实现
class Person implements IPerson, IRun {
star: string
age: number
constructor(star: string, age: number) {
this.star = star
this.age = age
}
sing() {
//
}
running() {
//
}
}
const p = new Person('zmg', 34)
console.log(p.star)
p.sing()
约束效果
interface InerOne {
name: string
age: number
}
// 报错
class Person implements InerOne {
name: string
// age: number
}
# 5、接口和抽象类区别
使用
重点关注是要捕捉子类的通用特性,还是要描述某些类该具备的一些行为。
以上面的案例为例,我们可以这样理解:
- 抽象类
abstract class Shape {
// 待实现方法
abstract getArea(): number
}
// Rectangle is a Shape
class Rectangle extends Shape {
//
}
- 接口
interface IPerson {
star: string
age: number
sing: () => void
}
interface IRun {
running: () => void
}
// Person has sing/running in (Iperson, IRun)
class Person implements IPerson, IRun {
//
}
# 二、泛型编程
- Type:
T
- key、value:
K
、V
- Element:
E
- Object:
O
# 1、类型参数化
在开始前,先思考一下下面的这个例子,看看有没有其他方式可以实现:
function demo(arg: number | string): number | string {
return arg
}
demo(1)
demo('lencamo')
答案
function demo<T>(arg: T): T {
return arg
}
demo<number>(1)
demo<string>('lencamo')
看到答案后,我们可以明显的发现:
在定义函数的时候,我们并没有预选指定具体的类型,而是在使用的时候指定类型,其实这就是使用泛型的一大好处
当然,使用泛型的范围还包括函数、接口、类等。此处除外,我们还可以同时使用多个泛型
function demo<T, U>(arg1: T, arg2: U) {
//
}
// 完整写法
demo<number, string>(1, '2')
demo<number[], boolean>([1, 2, 3], true)
// 省略写法
demo(1, '2')
demo([1, 2, 3], true)
# 2、泛型接口
同样,我们先来看看一个问题:
function demo<T>(arg: T): void {
console.log(arg.length) // 报错:类型"T"上不存在属性"length"
}
demo<string>('lencamo')
那如何解决呢?
答案 —— 泛型约束
通过 Ctrl + 鼠标右键,可以发现泛型 T 本身就是一个 interface
function demo<T extends string | number[]>(arg: T): void {
console.log(arg.length)
}
demo<string>('lencamo')
demo<number[]>([1, 2, 3])
相应的,我们还可以 extends 自定义的 interface:
interface ILen {
length: number
}
function demo<T extends ILen>(arg: T): void {
console.log(arg.length)
}
demo<number[]>([1, 2, 3])
关于泛型接口的使用,我们往往是用在增强 interface 的类型灵活性上面:
interface IDemo<T> {
id: T
name: string
}
const lisi: IDemo<number> = {
id: 5,
name: 'lisi'
}
const wangwu: IDemo<string> = {
id: '207st2474x023t3',
name: 'wangwu'
}
拓展:默认值
interface IDemo<T = string> {
id: T
name: string
}
const zhangsan: IDemo = {
id: '207st2474x023t3',
name: 'lisi'
}
# 3、泛型类
class Person<T, U> {
_name: T
_age: U
constructor(name: T, age: U) {
this._name = name
this._age = age
}
}
let p = new Person<string, number>('lencamo', 20)
# 4、泛型约束 ✨
在泛型接口中,已经简单的接触了泛型约束的使用,下面我们进一步学习:
- keyof
类似于联合类型
interface IPerson {
name: string
age: number
}
type ITypes = keyof IPerson // 相当于 'name' | 'age'
使用示例
function getObjectProperty<O, K extends keyof O>(obj: O, key: K) {
return obj[key]
}
const info = {
name: 'lencamo',
age: 20,
height: 180
}
const nameValue = getObjectProperty(info, 'name')
console.log(nameValue)
# 5、类型映射 ✨
有时候,我们的一个类型需要基于另外一个类型,但是我们又不想直接拷贝一份,此时我们就可以是映射类型
映射类型不能使用 interface 进行定义,在写法上我们可以与 😂 索引签名做一下比较。
interface Iperson {
name: string
age: number
}
// 类型映射
type MapType<T> = {
[key in keyof T]: T[key]
}
type NewPerson = MapType<Iperson>
const ldh: NewPerson = {
//
}
拓展:类型映射与修饰符
interface Iperson {
name: string
age?: number
readonly height: number
address?: string
}
type MapType<T> = {
// 删除readonly,删除可选类型?
-readonly [key in keyof T]-?: T[key]
}
type NewPerson = MapType<Iperson>
# 三、装饰器*
所谓装饰器,通俗的讲就是锦上添花。举个例子:花就是装饰器,而锦就是类、属性、方法、参数等。
我们可以通过将附加的方式,向类、属性、方法、参数身上扩展类、属性、方法、参数等
装饰器本质上是一个函数,它接受三个参数,分别是被装饰的目标、属性名称以及属性描述符。
使用 ts 的装饰器时,要先在 tsconfig.json 中配置:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
# 1、类装饰器
类装饰器是不能传参的
function demo(target: any) {
target.prototype.username = 'lencamo'
target.age = 19
}
@demo
class Person {
//
}
let p = new Person()
// @ts-ignore
console.log(p.username)
// @ts-ignore
console.log(Person.age)
那如果我们硬要传参呢?
可以使用装饰器工厂
function demo(options: any) {
return function (target: any) {
target.prototype.username = options.name
target.age = 19
}
}
@demo({ name: 'lencamo' })
class Person {
//
}
let p = new Person()
// @ts-ignore
console.log(p.username)
// @ts-ignore
console.log(Person.age)
# 2、属性装饰器
参考:
https://www.tslang.cn/docs/handbook/decorators.html
# 3、方法装饰器
参考:
https://www.tslang.cn/docs/handbook/decorators.html