跳转到内容

刀刀

4/2/2023

0 字

0 分钟

定义

下面是使用 TS 约束属性并实例化对象

js
class User {
    name: string
    age: number
    constructor(n: string, a: number) {
        this.name = n
        this.age = a;
    }

    info(): string {
        return `${this.name}的年龄是 ${this.age}`
    }
}

const fn = new User('刀刀', 12); // 刀刀的年龄是 12
const data = new User('小刀', 18) // 小刀的年龄是 18

通过约束数组的类型为User,使其成员只能是 User 类型对象

js
//...
const users: User[] = [fn, data];
console.log(users); // [{name: '刀刀', age: 12}, {name: '小刀', age: 18}]
//...

修饰符

public

第一个访问修饰符 public,指公开的属性或方法

  • 默认情况下属性是 public (公开的),即可以在类的内部与外部修改和访问
  • 不明确设置修饰符即为 public
ts
class User {
    public name: string
    public age: number
    constructor(n: string, a: number) {
        this.name = n
        this.age = a;
    }

    public info(): string {
        return `${this.name}的年龄是 ${this.age}`
    }
}

const fn = new User('刀刀', 12);
const data = new User('小刀', 18)

fn.name = "刀刀博客"

for (const key in fn) {
    if (fn.hasOwnProperty(key)) {
        console.log(key); // 刀刀博客 12
    }
}

protected

protected 修饰符指受保护的,只允许在父类与子类使用,不允许在类的外部使用

js
class fn {
    protected name: string
    constructor(name: string) {
        this.name = name
    }
}
class User extends fn {
    constructor(name: string) {
        super(name)
    }

    public info(): string {
        return `你好 ${this.name}`
    }
}

const fn = new User('刀刀');

console.log(User.info());
console.log(fn.name); //属性是 protected 不允许访问

private

private 修饰符指私有的,不允许在子类与类的外部使用

父类声明 private 属性或方法子类不允许覆盖

js
class fn {
    private name: string
    constructor(name: string) {
        this.name = name
    }
    private info(): void { }
}
class User extends fn {
    constructor(name: string) {
        super(name)
    }

    public info(): void {

    }
}

子类不能访问父类的 private 属性或方法

js
class fn {
    private name: string
    constructor(name: string) {
        this.name = name
    }
}
class User extends fn {
    constructor(name: string) {
        super(name)
    }

    public info(): string {
        return `你好 ${this.name}` //属性是 private 不允许子类访问
    }
}

子类更改父类方法或属性的访问修饰符有些限制的

  • 父类的 private 不允许子类修改
  • 父类的 protected 子类可以修改为 protected 或 public
  • 父类的 public 子类只能设置为 public
js
class fn {
    private name: string
    constructor(name: string) {
        this.name = name
    }
    public info(): void { }
    protected show(): void { }
}
class User extends fn {
    constructor(name: string) {
        super(name)
    }
    protected info(): string {
        return 'daodao.com'
    }
	private show(): void { } // 报错,不能比父类权限大,父类是受保护的型,子类不能设置为私有的
}

readonly

readonly 将属性定义为只读,不允许在类的内部与外部进行修改

  • 类似于其他语言的 const 关键字
js
class Axios {
    readonly site: string = 'https://daodao.com/api'
    constructor(site?: string) {
        this.site = site || this.site
    }
    public get(url: string): any[] {
        console.log(`你正在请求 ${this.site + '/' + url}`)
        return []
    }
}

const instance = new Axios('https://www.baidu.com')
instance.get('users')

constructor

构造函数是初始化实例参数使用的,在 TS 中有些细节与其他程序不同

我们可以在构造函数 constructor 中定义属性,这样就不用在类中声明属性了,可以简化代码量

  • 必须要在属性前加上 public、private、readonly 等修饰符才有效
  • 如果不加,代码编辑器就会报错,显示类里没有这个属性
js
class User {
    constructor(
        public name: string,
        public age: number
    ) {}

    public info(): string {
        return `${this.name}的年龄是 ${this.age}`
    }
}

const fn = new User('刀刀', 12);

static

static 用于定义静态属性或方法,属性或方法是属于构造函数的

静态属性是属于构造函数的,不是对象独有的,所以是所有对象都可以共享的

语法介绍

下面是 static 使用的语法

js
class Site {
    static word: string = '退路已经遥遥无期'

    static getSiteInfo() {
        return '前端之路渐行渐远' + Site.word
    }
}
console.log(Site.getSiteInfo());

const instance = new Site()
console.log(instance.getSiteInfo()) // 报错

单例模式

当把 construct 定义为非 public 修饰符后,就不能通过这个类实例化对象了。

js
class User {
    protected constructor() {

    }
}

const fn = new User();//报错

我们可以利用这个特性再结合 static 即可实现单例模式,即只实例化一个对象

js
class User {
    static instance: User | null = null;
    protected constructor() { }

    public static make(): User {
        if (User.instance == null) User.instance = new User; // 通过判断,只能生成一个User实例

        return User.instance;
    }
}

const fn = User.make();
console.log(fn);

get/set

使用 get 与 set 访问器可以动态设置和获取属性,类似于 vue 或 laravel 中的计算属性

js
class User {
    private _name
    constructor(name: string) {
        this._name = name
    }
    public get name() {
        return this._name;
    }
    public set name(value) {
        this._name = value
    }
}

const fn = new User('小刀')
fn.name = '李四'
console.log(fn.name);

因为 get 与 set 是新特性所以编译时要指定 ES 版本

js
tsc 1.ts -w -t es5

abstract

抽象类定义使用 abstract 关键字,抽象类除了具有普通类的功能外,还可以定义抽象方法

  • 抽象类可以不包含抽象方法,但抽象方法必须存在于抽象类中
  • 抽象方法是对方法的定义,子类必须实现这个方法
  • 抽象类不可以直接使用,只能被继承
  • 抽象类类似于类的模板,实现规范的代码定义
js
class Animation {
    protected getPos() {
        return { x: 100, y: 300 }
    }
}

class Tank extends Animation {
    public move(): void {

    }
}

class Player extends Animation {
    public move: void{}
}

上例中的子类都有 move 方法,我们可以在抽象方法中对其进行规范定义

  • 抽象方法只能定义,不能实现,即没有函数体
  • 子类必须实现抽象方法
js
abstract class Animation {
    abstract move(): void
    protected getPos() {
        return { x: 100, y: 300 }
    }
}

class Tank extends Animation {
    public move(): void {

    }
}

class Player extends Animation {
    public move(): void {

    }
}

子类必须实现抽象类定义的抽象属性

js
abstract class Animation {
    abstract move(): void
    abstract name: string
    protected getPos() {
        return { x: 100, y: 300 }
    }
}
class Tank extends Animation {
    name: string = '坦克'
    public move(): void {

    }
}

class Player extends Animation {
    name: string = '玩家'

    public move(): void {

    }
}

class Test extends Animation {} // 报错,没有定义抽象类

抽象类不能被直接使用,只能被继承

js
abstract class Animation {
    abstract move(): void
    protected getPos() {
        return { x: 100, y: 300 }
    }
}
const fn = new Animation(); //报错,不能通过抽象方法创建实例

Interface

接口用于描述类和对象的结构

  • 使项目中不同文件使用的对象保持统一的规范
  • 使用接口也会支有规范更好的代码提示

抽象类

下面是抽象类与接口的结合使用

js
interface AnimationInterface {
    name: string
    move(): void
}
abstract class Animation {
    protected getPos(): { x: number; y: number } {
        return { x: 100, y: 300 }
    }
}

class Tank extends Animation implements AnimationInterface {
    name: string = '敌方坦克'
    public move(): void {
        console.log(`${this.name}移动`)
    }
}

class Player extends Animation {
    name: string = '玩家'
    public move(): void {
        console.log(`${this.name}坦克移动`)
    }
}
const fn = new Tank()
const play = new Player()
fn.move()
play.move()

对象

下面使用接口来约束对象

js
interface UserInterface {
    name: string;
    age: number;
    isLock: boolean;
    info(other:string): string,
}

const fn: UserInterface = {
    name: '刀刀',
    age: 18,
    isLock: false,
    info(o:string) {
        return `${this.name}已经${this.age}岁了,${o}`
    },
}

console.log(fn.info());

如果尝试添加一个接口中不存在的函数将报错,移除接口的属性也将报错。

js
const fn: UserInterface = {
		...
    fn() { }  //“fn”不在类型“UserInterface”中
}

如果有额外的属性,使用以下方式声明,这样就可以添加任意属性了

js
interface UserInterface {
    name: string;
    age: number;
    isLock: boolean;
    [key:string]:any
}

接口继承

下面定义游戏结束的接口 PlayEndInterface ,AnimationInterface 接口可以使用 extends 来继承该接口

js
interface PlayEndInterface {
    end(): void
}
interface AnimationInterface extends PlayEndInterface {
    name: string
    move(): void
}

class Animation {
    protected getPos(): { x: number; y: number } {
        return { x: 100, y: 300 }
    }
}

class Tank extends Animation implements AnimationInterface {
    name: string = '敌方坦克'
    public move(): void {
        console.log(`${this.name}移动`)
    }
    end() {
        console.log('游戏结束');
    }
}

class Player extends Animation implements AnimationInterface {
    name: string = '玩家'
    public move(): void {
        console.log(`${this.name}坦克移动`)
    }
    end() {
        console.log('游戏结束');
    }
}
const fn = new Tank()
const play = new Player()
fn.move()
play.move()

对象可以使用实现多个接口,多个接口用逗号连接

js
interface PlayEndInterface {
    end(): void
}
interface AnimationInterface {
    name: string
    move(): void
}

class Animation {
    protected getPos(): { x: number; y: number } {
        return { x: 100, y: 300 }
    }
}

class Tank extends Animation implements AnimationInterface, PlayEndInterface {
    name: string = '敌方坦克'
    public move(): void {
        console.log(`${this.name}移动`)
    }
    end() {
        console.log('游戏结束');
    }
}

class Player extends Animation implements AnimationInterface, PlayEndInterface {
    name: string = '玩家'
    public move(): void {
        console.log(`${this.name}坦克移动`)
    }
    end() {
        console.log('游戏结束');
    }
}
const fn = new Tank()
const play = new Player()
fn.move()
play.move()

函数

下面使用 UserInterface 接口约束函数的参数与返回值

  • 会根据接口规范提示代码提示
  • 严格约束参数类型,维护代码安全

函数参数

下面是对函数参数的类型约束

js
interface UserInterface {
    name: string;
    age: number;
    isLock: boolean;
}

function lockUser(user: UserInterface, state: boolean): UserInterface {
    user.isLock = state;
    return user;
}

let user: UserInterface = {
    name: '刀刀', age: 18, isLock: false
}

lockUser(user, true);
console.log(user);

函数声明

使用接口可以约束函数的定义

js
interface Pay {
    (price: number): boolean
}
const getUserInfo: Pay = (price: number)=>true

构造函数

下面的代码我们发现需要在多个地方使用对 user 类型的定义

js
class User {
    info: { name: string, age: number }
    constructor(user: { name: string, age: number }) {
        this.info = user
    }
}
const fn = new User({ name: '刀刀', age: 18 })
console.log(fn);

使用 interface 可以优化代码,同时也具有良好的代码提示

js
interface UserInterface {
    name: string,
    age: number
}
class User {
    info: UserInterface
    constructor(user: UserInterface) {
        this.info = user
    }
}
const fn = new User({ name: '刀刀', age: 18 })
console.log(fn);

数组

对数组类型使用接口进行约束

js
interface UserInterface {
    name: string,
    age: number,
    isLock: boolean
}
const fn: UserInterface = {
    name: '刀刀',
    age: 18,
    isLock: false
}

const data: UserInterface = {
    name: '小刀',
    age: 16,
    isLock: false
}

const users: UserInterface[] = [];
users.push(fn, data)
console.log(users);

枚举

下面是使用枚举设置性别

js
enum SexType {
    BOY, GIRL
}

interface UserInterface {
    name: string,
    sex: SexType
}

const fn: UserInterface = {
    name: '刀刀',
    sex: SexType.GIRL
}
console.log(fn); //{ name: '刀刀', sex: 1 }

案例

下面是 index.ts 文件的内容,通过 interface 接口来限制支付宝与微信支付的规范

js
interface PayInterace {
    handle(price: number): void
}

class AliPay implements PayInterace {
    handle(price: number): void {
        console.log('支付宝付款');
    }
}
class WePay implements PayInterace {
    handle(price: number): void {
        console.log('微信付款');
    }
}

//支付调用
function pay(type: string, price: number): void {
    let pay: PayInterace
    if (type == 'alipay') {
        pay = new AliPay()
    } else {
        pay = new WePay()
    }
    pay.handle(price)
}

然后执行编译

js
tsc index.ts -w

界面处理 index.html

html
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>刀刀</title>
        <script src="index.js" defer></script>
    </head>
    <body>
        <button onclick="pay('alipay',100)">支付宝</button>
        <button onclick="pay('wepay',200)">微信付款</button>
    </body>
</html>

type

type 与 interface 非常相似都可以描述一个对象或者函数,使用 type 用于定义类型的别名,是非常灵活的类型定义方式。

  • type 可以定义基本类型别名如联合类型,元组
  • type 与 interface 都是可以进行扩展
  • 使用 type 相比 interface 更灵活
  • 如果你熟悉其他编程语言,使用 interface 会让你更亲切
  • 使用类(class) 时建议使用接口,这可以与其他编程语言保持统一
  • 决定使用哪个方式声明类型,最终还是看公司团队的规范

基本使用

下面是使用 type 声明对象类型

js
type User = {
    name: string,
    age: number
}
const fn: User = { name: '刀刀', age: 18 }

上面已经讲解了使用 interface 声明函数,下面来看使用 type 声明函数的方式

js
type Pay = (price: number) => boolean
const wepay: Pay = (price: number) => {
    console.log(`微信支付${price}`);
    return true;
}

wepay(100)

类型别名

type 可以为 number、string、boolean、object 等基本类型定义别名,比如下例的 IsAdmin。

js
//基本类型别名
type IsAdmin = boolean

//定义联合类型
type Sex = 'boy' | 'girl'

type User = {
    isAdmin: IsAdmin,
    sex: Sex
}
const fn: User = {
    isAdmin: true,
    sex: "boy"
}

//声明元组
const users: [User] = [fn]

索引类型

type 与 interface 在索引类型上的声明是相同的

js
interface User {
    [key: string]: any
}

type UserTYpe = {
    [key: string]: any
}

声明继承

typescript 会将同名接口声明进行合并

js
interface User {
    name: string
}
interface User {
    age: number
}
const fn: User = {
    name: '刀刀',
    age: 18
}

interface 也可以使用 extends 继承

js
interface Admin {
    role: string
}
interface User extends Admin {
    name: string
}
const fn: User = {
    role: 'admin',
    name: '刀刀',
}

interface 也可以 extends 继承 type

js
type Admin = {
    role: string
}
interface User extends Admin {
    name: string
}
const fn: User = {
    role: 'admin',
    name: '刀刀',
}

type 与 interface 不同,存在同名的 type 时将是不允许的

js
type User {
    name: string
}
type User {
    age: number
}

不过可以使用& 来进行 interface 的合并

js
interface Name {
    name: string
}
interface Age {
    age: number
}
type User = Name & Age

下面是 type 类型的声明合并

js
type Admin = {
    role: string,
    isSuperAdmin: boolean
}
type Member = {
    name: string
}

type User = Admin & Member;

const fn: User = {
    isSuperAdmin: true,
    role: 'admin',
    name: '刀刀'
}

下面声明的是满足任何一个 type 声明即可

js
type Admin = {
    role: string,
    isSuperAdmin: boolean
}
type Member = {
    name: string
}

type User = Admin | Member;

const fn: User = {
    role: 'admin',
    name: '刀刀'
}

implements

class 可以使用 implements 来实现 type 或 interface

js
type Member = {
    name: string
}

class User implements Member {
    name: string = '刀刀'
}