一、定义对象类型
在TypeScript中定义对象类型有以下三种方式:
1. 匿名对象类型
匿名对象类型是在定义变量时直接使用花括号{},来定义一个对象类型。例如:
const person: { name: string, age: number } = { name: 'John', age: 25 };
上述代码中定义了一个person变量,它的类型为对象,它有两个属性:name和age,其中name属性的类型为字符串,age属性的类型为数字。
2. 接口类型
使用接口来定义对象类型,可以使代码更加可读、易于维护。例如:
interface Person {name: string;age: number;
}const person: Person = { name: 'John', age: 25 };
上述代码中,定义了一个名为Person的接口,其中包括了两个属性:name和age。然后使用Person接口来定义了一个person变量,它的类型为Person接口。
3. 类型别名
使用类型别名可以为对象类型定义简短、易读的名称。例如:
type Person = {name: string;age: number;
}const person: Person = { name: 'John', age: 25 };
上述代码中,使用type关键字定义了一个名为Person的类型别名,并通过花括号{}来定义了一个对象类型,其中有两个属性:name和age。然后使用Person类型别名来定义了一个person变量,它的类型为Person类型别名。
二、对象类型中的属性修改器
1. 可选属性
TypeScript中的可选属性指的是,在定义对象类型时,可以设置一些属性为可选属性,即不是必须存在的属性。具体的语法是在属性名称后面加上一个问号(?
),如下所示:
interface Person {name: string;age?: number;gender?: string;
}
在上面的例子中,age
和gender
是可选属性。也就是说,在声明一个Person
类型的对象时,可以只包含name
属性,而不必提供age
和gender
属性。如果提供了这两个属性,它们的值必须符合对应的类型定义。
下面分多个角度举例说明可选属性的用法和好处。
(1). 增强代码的灵活性
使用可选属性可以增强代码的灵活性,使得我们在声明对象的时候可以根据需要选择性地添加属性,而不是强制要求属性必须存在。这样一来,一些在某些场景下没有用处的属性就不必被强制赋值了,节约了代码修改的时间和精力。
举个例子,我们在定义一个Book
接口时,可以将author
、publisher
和description
属性都定义为可选属性:
interface Book {title: string;author?: string;publisher?: string;description?: string;
}
然后在使用这个接口定义对象的时候,可以按照需求来添加这些属性,比如:
const book1: Book = {title: 'TypeScript in Action',author: 'Erick Wendel'
};const book2: Book = {title: 'JavaScript: The Good Parts',author: 'Douglas Crockford',publisher: 'Yahoo Press'
};const book3: Book = {title: 'JavaScript: The Definitive Guide',author: 'David Flanagan',description: 'This book provides a complete description of the JavaScript language.'
};
在以上例子中,我们在声明book1
时只提供了title
和author
属性,因为它们是必需的属性。而在声明book2
时,我们增加了publisher
属性,因为这个属性在这个场景下是有用的。在声明book3
时,我们只提供了title
和author
属性,同时利用了description
属性来描述这本书的内容。所以,这样的语法是非常方便的。
(2). 构建可靠的对象类型检查
使用可选属性还能帮助我们构建可靠的对象类型检查。
在JavaScript中,我们会遇到无效的对象属性,因为它们没被正确地定义或被更新。这些错误通常很难发现,结果会导致代码崩溃或是运行出现错误。
TypeScript可以用类型检查来防止这种问题。通过定义对象属性为可选属性,我们可以预防掉对象属性定义时的错误,从而构建可靠地类型检查系统。如果属性被定义成可选的,那么它可以没有定义,而且也不会触发错误。这样我们就可以更容易地处理这些异常情形,从而提高代码的可靠性和维护性。
下面是一个例子展示了使用可选属性来保证类型检查的可靠性:
interface Car {brand: string;model: string;year?: number;
}function getCarInfo(car: Car): string {return `Brand: ${car.brand}, Model: ${car.model}${car.year ? `, Year: ${car.year}` : ''}`;
}const car1: Car = { brand: 'Tesla', model: 'Model S', year: 2018 };
const car2: Car = { brand: 'BMW', model: 'X5' };
const car3: Car = { brand: 'Mercedes', model: 'E220', year: '2021' };console.log(getCarInfo(car1)); // Brand: Tesla, Model: Model S, Year: 2018
console.log(getCarInfo(car2)); // Brand: BMW, Model: X5
console.log(getCarInfo(car3)); // Brand: Mercedes, Model: E220
在上面的例子中,我们定义了一个Car
接口,并定义year
属性为可选属性。可以清晰地看到,我们在getCarInfo
函数中使用了year
属性的值来返回车的信息。当year
属性可选时,我们在获取year
属性时需要先检查它是否存在,否则输出的字符串结果将不符合我们预期。在car3
的定义中,我们给year
属性赋的值是一个字符串型而不是数字型,这时TypeScript会提示错误。
(3). 提供默认属性值
除了类型检查外,我们还可以用可选属性来提供默认属性值。在某些场景下,如果对象的某些属性没有被定义,那么我们可以提供一个默认值,以确保代码可以正常运行。
举个例子,在定义一个Product
接口时可以使用可选属性提供一个price
属性的默认值,如下所示:
interface Product {name: string;price?: number;
}const shirt: Product = { name: 'Shirt' };
const pants: Product = { name: 'Pants', price: 45.00 };console.log(shirt.price || 15.00); // 15
console.log(pants.price || 15.00); // 45
在上面的例子中,我们给shirt
对象定义了一个默认的price
属性值为15,在打印price
属性时,因为shirt
对象没有定义price
属性,所以输出的值是默认值15
。而在打印pants
的price
属性时,因为pants
对象定义了price
属性,输出的是45
,这是由于该属性已定义的值是45。
2. 只读属性
TypeScript中,我们可以声明一个对象类型中的属性为只读属性,即该属性的值一旦被赋值就不能再被修改。
举例来说,假设有一个Student类型的对象,其中包含学生的姓名和年龄。我们可以将学生的姓名声明为只读属性,代码如下:
type Student = {readonly name: string;age: number;
}
在上述代码中,我们使用了readonly关键字来将name属性声明为只读属性。这意味着一旦我们给该属性赋值,就无法再修改它的值。
下面再举一个例子,假设我们有一个Point类型的对象,包含了二维平面上的坐标x和y。我们可以将该对象中的x和y属性都声明为只读属性,以保证对象的坐标值不会被意外修改。代码如下:
type Point = {readonly x: number;readonly y: number;
}const p: Point = { x: 0, y: 0 };// 错误,无法修改只读属性
p.x = 10;
在上述代码中,我们使用了readonly关键字将Point类型中的x和y属性都声明为只读属性。在给p对象中的x属性赋值之后,我们试图修改该属性的值,但是因为它是只读属性,所以会编译错误。
3. 索引签名
在 TypeScript 中,对象类型可以包含索引签名,以支持在动态属性上访问属性值。索引签名允许您在对象类型中定义一个模式,该模式指定应该具有哪些属性和属性类型。以下是一个示例:
interface ExampleObject {[key: string]: string;
}const exampleObject: ExampleObject = {property1: "value1",property2: "value2",// ...
};
这里的索引签名 [key: string]: string
意味着对象 ExampleObject
中的所有属性的键都是字符串,所有属性的值都是字符串。因此,可以使用类似于 exampleObject.property1
这样的关键字为其属性赋值或访问属性值。
下面是一些其他角度的示例:
- 在对象类型中使用数字索引签名,以表示索引为数字的属性:
interface ExampleObject {[key: number]: string;
}const exampleObject: ExampleObject = {0: "value1",1: "value2",// ...
};
- 在对象类型中使用只读索引签名,以表示不希望在运行时更改的属性:
interface ExampleObject {readonly [key: string]: string;
}const exampleObject: ExampleObject = {property1: "value1",property2: "value2",// ...
};exampleObject.property1 = "new value"; // This will cause a TypeScript error
- 在对象类型中使用联合类型索引签名,以表示可以具有多个类型的属性:
interface ExampleObject {[key: string]: string | number;
}const exampleObject: ExampleObject = {property1: "value1",property2: 2,// ...
};
在这个示例中,属性的值可以是字符串或数字。
4. 扩展类型
TypeScript中的对象类型是通过接口来定义的,接口可以扩展其他接口,从而实现对象类型的扩展。
例如,我们可以定义一个基础的对象类型接口Person,然后定义一个Student接口来继承Person接口并添加一些额外的属性:
interface Person {name: string;age: number;
}interface Student extends Person {school: string;grade: string;
}
这样,Student接口就包含了Person接口中所有的属性,同时添加了school和grade属性。
5. 交叉类型
交叉类型(Intersection Types)是Typescript中的一种类型操作符,用于将多个类型合并成一个类型。它的语法是将多个类型通过 & 连接起来,例如:
type Person = {name: string;age: number;
};
type Employee = {employer: string;salary: number;
};type Worker = Person & Employee;
在上面的例子中,我们定义了两个类型Person和Employee,并将它们通过 & 连接起来得到了一个新的类型Worker。这个新类型包含了两个原类型中的所有属性。
与交叉类型类似的还有联合类型(Union Types),用于将多个类型中的一个合并成一个类型,并采用 | 连接。
交叉类型和interface的extends扩展类型的区别
与交叉类型相比,使用interface的extends扩展类型可以实现类似的效果,但是它们的设计思想不同。extends用于在一个类型基础上扩展属性和方法,而交叉类型则是将多个类型合并起来以创建一个新的类型。
例如,我们定义了一个接口Animal:
interface Animal {name: string;eat(): void;
}
然后定义了两个接口Dog和Person,并通过extends方式扩展了它们的属性和方法:
interface Dog extends Animal {bark(): void;
}interface Person extends Animal {age: number;speak(): void;
}
上面代码中,Dog和Person都扩展了Animal接口,即它们都继承了Animal中的name属性和eat方法。与交叉类型不同的是,Dog和Person无法同时拥有Animal以外的属性和方法。
综上所述,交叉类型和extends扩展类型虽然都用于继承和合并类型,但是它们的应用场景和用途不同。交叉类型
适合于将多个类型合并为一个类型,而extends扩展类型
适合于在一个类型基础上扩展属性和方法。在不同的场景中,我们可以选择不同的方式来定义和组合类型。
6. 泛型对象类型
泛型对象类型可以用于对象属性中的类型声明。例如,以下代码定义了一个对象类型,该对象具有不同类型的属性:
interface List<T> {data: T[]add: (item: T) => void
}const list1: List<string> = {data: ['hello', 'world'],add(item) {this.data.push(item)}
}const list2: List<number> = {data: [1, 2],add(item) {this.data.push(item)}
}
在上面的代码中,<T>
表示泛型对象类型,我们在List<T>
中使用了该类型,以声明data
属性和add
方法的参数和返回类型。