属性或方法的修饰符
在对象类型中,不仅可以为每个属性指定名称及类型,还可以用修饰符为此属性赋予特定的性质。属性修饰符主要有 3 种——可选修饰符、只读修饰符和索引签名。接下来,将一一介绍。
可选修饰符
默认情况下,对象类型字面量或接口中定义了多少个属性和方法,在使用它创建具体对象时就需要传入多少个属性和方法,而且类型必须符合其定义,否则会引起编译错误,示例代码如下。
interface CalculationOf2Numbers {
num1: number,
num2: number,
calculate: () => number
}
let calculator: CalculationOf2Numbers;
//编译错误:类型"{ num1: number; }"缺少类型"CalculationOf2Numbers"中的以下属性: num2,
//calculatets(2739)
calculator = { num1: 1 };
但在一些情况下,某些属性或方法可能并不是必需的,因此就需要使用可选修饰符。在 TypeScript 中,通过在属性或方法后添加问号将其定义为可选的,然后在创建具体对象或赋值时,这些可选属性或方法可有可无。例如,以下代码均能正确编译。
interface CalculationOf2Numbers {
num1: number,
num2?: number,
calculate?: () => number
}
let calculator: CalculationOf2Numbers;
calculator = { num1: 1 };
calculator = { num1: 1, num2: 2 };
calculator = {
num1: 1,
calculate: function () {
return this.num1;
}
};
calculator = {
num1: 1,
num2: 2,
calculate: function () {
return this.num1 + this.num2;
}
};
只读修饰符
对象的属性和方法默认情况下是支持读写的。第 6 章提到,const
关键字只能限定栈上的内容不可编辑,而对象是引用类型,其数据存储在堆上,因此 const
关键字只能限定引用的对象地址不变,但无法限定堆上的属性和方法不变。例如,在以下代码中,虽然 calculator
的引用对象不可更改,但其属性值可以随意修改。
interface CalculationOf2Numbers {
num1: number,
num2: number,
calculate: () => number
}
const calculator: CalculationOf2Numbers = { num1: 1, num2: 2, calculate: function ()
{ return this.num1 + this.num2; } };
//以下代码会引起编译错误
//编译错误:无法分配到 "calculator",因为它是常数。ts(2588)
calculator = { num1: 3, num2: 4, calculate: function () { return 1; } }
//以下代码可以正确编译
calculator.num1 = 3;
calculator.num2 = 4;
calculator.calculate = function () { return 1; };
要限定对象的属性和方法不可编辑,就需要在属性声明或方法声明的前面加上 readonly
关键字。例如,在以下代码中,对只读属性或方法进行编辑会引起编译错误。
interface CalculationOf2Numbers {
readonly num1: number,
readonly num2: number,
readonly calculate: () => number
}
const calculator: CalculationOf2Numbers = { num1: 1, num2: 2, calculate: function
() { return this.num1 + this.num2; } };
//编译错误:无法分配到 "num1",因为它是只读属性。ts(2540)
calculator.num1 = 3;
//编译错误:无法分配到 "num2",因为它是只读属性。ts(2540)
calculator.num2 = 4;
//编译错误:无法分配到 "calculate",因为它是只读属性。ts(2540)
calculator.calculate = function () { return 1; };
索引签名
在 TypeScript 中,如果实际赋值的对象的属性与方法比接口中定义的属性与方法多,那么将该对象的值赋给该接口类型会引起编译错误。例如,在以下代码中,接口中只定义了 name
和 age
属性,但实际赋值的对象还有 height
属性,这会引起编译错误。
interface Person {
name: string,
age: number
}
let person1: Person = { name: "Kiddy", age: 17 };
//编译错误:不能将类型"{ name: string; age: number; height: number; }"分配给类型"Person"。
//对象文字可以只指定已知属性,并且"height"不在类型"Person"中。ts(2322)
let person2: Person = { name: "Shark", age: 15, height: 180 };
要解决这个问题,用前面提到的可选修饰符 ?
将 height
作为可选参数加入接口定义中。但这只适用于已知有哪些可选属性或方法的情况,如果遇到完全不确定有哪些可选属性或方法的情况应该怎么办呢?这时就需要用到索引签名。通过索引签名,让接口支持任意数量的可选属性。索引签名的定义方式如下。
interface 接口名称 {
...
[索引名称:索引类型]:属性类型;
...
}
TypeScript 只支持两种类型的索引——字符串索引和数值索引。如果使用字符串索引,则表示这些任意数量的可选属性名称只能是字符串(数字也是字符串);如果使用数值索引,则表示这些任意数量的可选属性名称只能是数字。
-
字符串索引
以下代码定义了一个字符串索引,这意味着属性名称可以是任意字符串,它支持存储
any
类型的值,因此支持任意类型的可选属性和方法。interface Person { name: string, age: number, [index: string]: any } let person1: Person = { name: "Kiddy", age: 17 }; let person2: Person = { name: "Shark", age: 15, height: 180 }; let person3: Person = { name: "Annie", age: 10, height: 120, sex: "male" }; let person4: Person = { name: "Aiken", age: 25, height: 174, sayHello: function () { console.log("hello!") } };
-
数值索引
如果定义为数值索引,则属性名称只能由数字组成;否则,会引起编译错误,示例代码如下。
interface Person { name: string, age: number, [index: number]: any } let person1: Person = { name: "Kiddy", age: 17 }; let person2: Person = { name: "Shark", age: 15, 1: 180 }; let person3: Person = { name: "Annie", age: 10, 1: 120, 2: "male" }; let person4: Person = { name: "Aiken", age: 25, 1: 174, 3: function () { console.log ("hello!") } }; //编译错误:不能将类型"{ name: string; age: number; sex: string; }"分配给类型"Person" //对象文字可以只指定已知属性,并且"sex"不在类型"Person"中。ts(2322) let person5: Person = {name:"Error", age:16, sex:"male"};
-
索引与已有属性的关系
严格来说,索引表示所有属性或方法不仅包含未定义的属性或方法,还包含已定义的所有属性和方法。例如,之前定义的
Person
接口拥有name
和age
两个属性,这意味着如果定义字符串索引,name
和age
这两个名称会匹配字符串索引。因此在定义字符串索引时,其属性类型至少需要包含name
和age
的属性类型,否则会引起编译错误。以下代码定义了一个属性类型为字符串的字符串索引,但字符串索引除包含未定义的属性之外,还包含
name
和age
这两个已定义的属性。如果索引的属性类型定义为string
,就无法包含age
的number
类型,因此会引起编译错误。interface Person { name: string, //编译错误:类型"number"的属性"age"不能赋给"string"索引类型"string"。ts(2411) age: number, [index: string]: string }
要解决这个问题,参考前面的示例,将索引的属性类型定义为
any
类型。但any
类型不仅包含string
类型和number
类型,还支持其他类型。如果能确定所有属性仅为string
类型和number
类型,则可以将索引的属性类型定义为string | number
(联合类型,可以用 “|” 连接多个类型,后面会详细介绍),这样所有未定义的属性的值既可以为number
类型也可以为string
类型,同时这种类型包含已定义的name
属性和age
属性的类型,不再引起编译错误,代码如下。interface Person { name: string, age: number, [index: string]: string | number }
-
其他支持索引的类型
除对象之外,其他引用类型也支持带有索引的接口,例如,数组支持数值索引,代码如下。
interface StringArray {
[index: number]: string
}
let array1: StringArray = ["x", "y", "x"];
以上代码声明了一个 StringArray
接口,它拥有数值索引,属性类型为字符串,该接口和字符串数组匹配,因此可以将数组的值赋给该接口类型的变量。