对象的声明

对象的基本声明方式如下。

let 对象名称 = {
    属性名称1: 属性值1,
    属性名称2: 属性值2,
    ...
    方法名称1: 函数声明或引用函数名称1,
    方法名称2: 函数声明或引用函数名称2,
    ...
}

对象可以是任何可整体描述的事物,如汽车、动物、手机、椅子都可以当作对象来描述。以手机为例,手机拥有系统、内存、尺寸、型号等属性,拥有开机、发短信、打电话等方法,因此我们不仅可以使用对象对其进行描述,还可以通过 “对象名称.属性名称” 读写它的属性,并以 “对象名称.方法名称” 调用它的方法,代码如下。

let myPhone = {
    system: "iOS 15",
    ram: 8,
    size: 6.4,
    model: "8 plus",
    turnOn: function () {
        console.log(`${this.system}欢迎界面`);
    },
    sendMessage: function (receiver: string, message: string): void {
        console.log(`发送短信中...接收人:${receiver};短信内容:${message}`);
    },
    makeCall: function (receiver: string): void {
        console.log(`拨打电话中...接听人:${receiver}`);
    }
}

//以下代码输出"该手机的内存大小为8GB,尺寸为6.4,型号为8 plus"
console.log('该手机的内存大小为${myPhone.ram}GB,尺寸为${myPhone.size},型号为${myPhone.model}');
//以下代码输出"iOS 15欢迎界面"

myPhone.turnOn();
//以下代码输出"发送短信中...接收人:12333333;短信内容:今天天气很好!"
myPhone.sendMessage("12333333", "今天天气很好!");
//以下代码输出"拨打电话中...接听人:1233333"
myPhone.makeCall("1233333");

使用对象类型字面量声明对象

TypeScript 中,使用对象类型字面量表示对象的类型,描述对象的内部结构,语法如下。

{
    属性名称1: 属性类型,
    属性名称2: 属性类型,
    ...
    方法名称1: 函数调用签名,
    方法名称2: 函数调用签名,
    ...
}

示例代码如下。

let person1: { firstName: string, lastName: string, selfIntroduction: () => void };

person1 = {
    firstName: "Rick",
    lastName: "Zhang",
    selfIntroduction: function () {
        console.log('My name is ${this.firstname} ${this.lastName}');
    }
}

//编译错误:类型"{ firstName: string; }缺少以下属性: lastName, selfIntroduction。 ts(2739)
person1 = {
    firstName: "error"
}

以上代码声明一个 person1 变量,其类型为 {firstName: string, lastName: string,selfIntroduction: ()=>void},因此为其赋值的对象的值也必须符合对象类型字面量中定义的结构。第一次为 person1 变量赋值的对象的值符合该结构,因此可以赋值成功;第二次为 person1 变量赋值的对象的值不符合该结构,因此会引起编译错误。

  1. 重载方法

    当使用对象类型字面量来表示对象的类型时,方法可以声明为重载方法。重载方法有两种声明形式,一种是使用重载签名,另一种是使用同名方法。如果对象类型字面量包含重载方法,那么赋值的具体对象中必须具有针对所有重载方法的单个具体实现方法,它的对应位置的参数类型和返回值类型必须能兼容前面所有重载签名中的类型;否则,会引起编译错误。

    使用重载签名的示例代码如下。

    let combineCaculator: {
        name: string,
        combine: {
            (a: boolean, b: boolean): boolean
            (a: string, b: string): string
            (a: number, b: number): number
        }
    }

    以上代码声明一个名为 combineCaculator 的变量,它拥有一个名为 name、类型为字符串的属性,还拥有一个 combine() 方法,该方法拥有 3 个重载签名,其参数分别为布尔类型、字符串类型,返回值为数值类型。

    使用同名方法的示例代码如下,其作用等同于上个示例的代码,TypeScript 会自动将同名方法识别为重载方法。

    let combineCaculator: {
        name: string,
        combine(a: boolean, b: boolean): boolean,
        combine(a: string, b: string): string,
        combine(a: number, b: number): number
    }

    无论使用哪种重载方法声明形式,后续在为对象赋值时,该方法的重载实现部分的参数和返回值都需要兼容前面的重载方法的类型。例如,定义成 any,以兼容前面所有签名的类型,然后通过类型运算符判断传入的参数值类型,并针对不同类型分别进行处理,代码如下。

    combineCaculator = {
        name: "Combine Caculator",
        //以下为具体实现部分
        //实现函数的参数与返回值类型必须兼容所有签名中对应位置的类型,否则引起编译错误
        combine: function (a: any, b: any): any {
            if (typeof a == "boolean" && typeof b == "boolean") {
                return a || b;
            }
            else {
                return a + b;
            }
        }
    }

    上述方法拥有 3 个类型参数和返回值均不同的重载方法,因此用对应的 3 种方式来调用,示例代码如下。

    //value1为number类型,值为3
    let value1 = combineCaculator.combine(1, 2);
    //value2为string类型,值为"ab"
    let value2 = combineCaculator.combine("a", "b");
    //value3为boolean类型,值为true
    let value3 = combineCaculator.combine(true, false);
  2. 其他应用场景及局限

    除在声明变量时使用对象类型字面量之外,你还可以在许多涉及类型的场景下使用对象类型字面量。例如,在以下代码中,将对象类型字面量用于声明函数和数组中。

    function introduction(person: { firstName: string, lastName: string }) {
        console.log(`My name is ${person.firstName} ${person.lastName}`);
    }
    
    let array1: { firstName: string, lastName: string }[] =
        [{ firstName: "Rick", lastName: "Zhong" },
        { firstName: "Alina", lastName: "Zhao" }];

    但不难发现,使用对象类型字面量来声明对象有诸多不便,当多处代码要使用同一种对象类型字面量时,就会出现很多冗长且重复的类型代码。要改善这样的情况,就需要使用类型别名或接口。

使用类型别名声明对象

通过使用类型别名,你可以给已有的类型起一个新的名称,该名称可以作为类型关键字使用。类型别名可以起到简化代码的作用,适用于各种对象类型。其定义方式如下。

type 类型别名 = 类型描述;

例如,以下代码定义一个名为 Person 的对象类型,该类型别名可以在各个场景下复用。

type Person = {
    firstName: string,
    lastName: string
}

function introduction(person: Person) {
    console.log(`My name is ${person.firstName} ${person.lastName}`);
}

let array1: Person[] =
    [{ firstName: "Rick", lastName: "Zhong" },
{ firstName: "Alina", lastName: "Zhao" }];

类型别名不仅可用于对象类型,还可以用于任何类型,后面将详细介绍。

使用接口声明对象

和类型别名类似,接口用于描述对象类型的结构,但接口只能用于描述对象类型,不能用于描述非对象类型,并且接口拥有继承和扩展的特性,更适合面向对象编程。如果要描述对象类型的结构,推荐优先使用接口而非类型别名。下面将统一使用接口来声明对象。

结构的定义语法如下。

interface 接口名称 {
    属性名称1: 属性类型,
    属性名称2: 属性类型,
    ...
    方法名称1: 函数调用签名,
    方法名称2: 函数调用签名,
    ...
}

示例代码如下。

interface Person {
    firstName: string,
    lastName: string
}

function introduction(person: Person) {
    console.log(`My name is ${person.firstName} ${person.lastName}`);
}

let array1: Person[] =
    [{ firstName: "Rick", lastName: "Zhong" },
{ firstName: "Alina", lastName: "Zhao" }];