【鸿蒙开发】TypeScript语言1:数据类型和接口
ArkTS在TypeScript(简称TS)生态基础上做了进一步扩展,保持了TS的基本风格,同时通过规范定义强化开发期静态检查和分析,提升代码健壮性,并实现更好的程序执行稳定性和性能。所以,要想学号ArkTS得先了解TS。在线编程:TS在线演练场在TypeScript中,使用let或const关键字来声明变量。let声明的变量可以被重新赋值,而const声明的变量则需要在声明时初始化,并且之后不能
ArkTS在TypeScript(简称TS)生态基础上做了进一步扩展,保持了TS的基本风格,同时通过规范定义强化开发期静态检查和分析,提升代码健壮性,并实现更好的程序执行稳定性和性能。
所以,要想学号ArkTS得先了解TS。
一、变量声明
在TypeScript中,使用let或const关键字来声明变量。let声明的变量可以被重新赋值,而const声明的变量则需要在声明时初始化,并且之后不能被重新赋值(对于对象或数组,这意味着不能改变它们的引用,但可以修改它们的属性或元素)。
变量后面可以跟随一个冒号和类型注解,来指定变量的类型。例如:
let hello = "Hello!";
const pp = 99;
变量命名规则:
- 只能出现数字、字母、下划线、美元符号($)
- 不能以数字开头
- TS区分大小写
推荐使用驼峰命名法进行变量命名。
类型推断:
TS会在没有明确的指定类型的时候推测出一个类型:
- 定义变量时赋值了, 推断为字面量对应的类型
- 定义变量时没有赋值, 推断为
any类型
二、 数据类型
TypeScript 中的数据类型分为了两大类:基本数据类型(原始数据类型)和复杂数据类型(对象类型)
TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
| 类型 | 表示 | 说明 |
|---|---|---|
| 布尔类型 | boolean |
true false |
| 数值类型 | number |
123 1.23 |
| 字符串类型 | string |
"abc" 'abc' |
| null/undefined类型 | null / undefined |
表示null和undefined本身,意义不大 |
| 任意类型 | any |
没有指定任何类型 |
| 数组类型 | Type[] |
number[] string[] |
| 联合类型 | number | string |
一个值可以是几种类型之一 |
| 字面量类型 | 'left' | 'center' | 'right' |
限制变量或参数的取值,只能是其中之一 |
| 函数类型 | () => void |
对函数的参数及返回值指定类型 |
| 对象类型 | {…} |
限定对象的结构(属性及方法) |
| 复杂类型 | interface接口 | |
| 泛型 | <T> |
在TypeScript中,类型系统基于JavaScript并进行了扩展,可分为原生类型(JavaScript固有的基础类型,TypeScript直接继承并增强)和特殊类型(TypeScript特有的、用于类型系统增强的类型)。
以下是TypeScript中原生类型和特殊类型的表格整理:
1、原生类型(JavaScript 基础类型,TypeScript 直接支持)
| 类型名称 | 描述 | 示例 |
|---|---|---|
number |
表示所有数字(整数、浮点数、NaN、Infinity 等) | let num: number = 3.14; |
string |
表示文本字符串(单引号、双引号、模板字符串均可) | let str: string = `Hello ${name}`; |
boolean |
表示布尔值,仅包含 true 和 false |
let flag: boolean = true; |
null |
表示“空值”,单值类型(严格模式下需显式声明) | let empty: null = null; |
undefined |
表示“未定义”,变量未赋值时的默认值(严格模式下需显式声明) | let unassigned: undefined = undefined; |
symbol |
ES6 引入的唯一标识符,通过 Symbol() 创建,值唯一 |
const key: symbol = Symbol('id'); |
bigint |
ES2020 引入的大整数类型,用于超出 number 精度范围的整数(后缀 n) |
let big: bigint = 9007199254740993n; |
object |
表示非原始类型(除上述7种外的类型,如对象、数组、函数等) | let obj: object = { name: 'Tom' }; |
2、特殊类型(TypeScript 特有,用于增强类型系统)
| 类型名称 | 描述 | 示例 |
|---|---|---|
any |
关闭类型检查,变量可赋值任意类型,可调用任意方法(不报错) | let value: any = 'hello'; value = 123; value.toFixed(); |
unknown |
安全的“未知类型”,使用前需通过类型检查或断言确认类型 | let val: unknown = 'test'; if (typeof val === 'string') { val.length; } |
void |
表示“无返回值”,常用于函数返回类型(可省略 return 或返回 undefined) | function log(): void { console.log('hi'); } |
never |
表示“永远不会发生的值”,用于函数永不正常结束的场景(如抛异常、死循环) | function error(): never { throw new Error('fail'); } |
| 元组(Tuple) | 固定长度、固定类型顺序的数组 | let user: [string, number] = ['张三', 18]; |
| 枚举(Enum) | 命名的常量集合,默认值从0递增(可手动指定) | enum Direction { Up = 1, Down, Left, Right } |
| 联合类型(Union) | 值可以是多种类型中的一种(用 | 分隔) |
let id: string | number = '123'; id = 456; |
| 交叉类型(Intersection) | 同时具有多种类型的属性(用 & 合并) |
type A = { name: string }; type B = { age: number }; type C = A & B; |
| 字面量类型(Literal) | 具体单值作为类型,只能取该值(常与联合类型结合限制取值范围) | type Status = 'success' | 'error' | 'pending'; |
一、 各类型说明
2.1 布尔
最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean。
let isDone: boolean = false;
2.2 数值
和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
2.3 字符串
可以使用双引号( ")或单引号(')表示字符串。
也使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围(`),并且以${ expr }这种形式嵌入表达式.
let name: string = "bob";
name = "smith";
let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;
//等价于下面:
let sentence: string = "Hello, my name is " + name + ".\n\n" +
"I'll be " + (age + 1) + " years old next month.";
2.4 数组
两种声明方式
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
2.5 元组 Tuple
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error
//当访问一个已知索引的元素,会得到正确的类型
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
//当访问一个越界的元素,会使用联合类型替代
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()); // OK, 'string' 和 'number' 都有 toString
x[6] = true; // Error, 布尔不是(string | number)类型
2.6 枚举
enum类型是对JavaScript标准数据类型的一个补充。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
//默认情况下,从0开始为元素编号。 也可以手动的指定成员开始的数值。
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
//或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
//枚举类型提供的一个便利是你可以由枚举的值得到它的名字。
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName); // 显示'Green'因为上面代码里它的值是2
2.7 Any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量。
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
//当你只知道一部分数据的类型时,any类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
let list: any[] = [1, true, "free"];
list[1] = 100;
在对现有代码进行改写的时候,any类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。 你可能认为 Object有相似的作用,就像它在其它语言中那样。 但是 Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
2.8 Void
某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
function warnUser(): void {
console.log("This is my warning message");
}
//声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
let unusable: void = undefined;
2.9 Null 和 Undefined
undefined和null两者各自有自己的类型分别叫做undefined和null。 和 void相似,它们的本身的类型用处不是很大:
// 本身赋值没啥用
let u: undefined = undefined;
let n: null = null;
默认情况下null和undefined是所有类型的子类型。 就是说你可以把 null和undefined赋值给number类型的变量。
然而,当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自。 这能避免 很多常见的问题。 也许在某处你想传入一个 string或null或undefined,你可以使用联合类型string | null | undefined。
2.10 Never
never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。
never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
2.11 Object、object 和 {}
在TypeScript中,Object、object 和 {} 看似相似,实则在类型约束和适用场景上有显著区别,主要体现在对“可赋值类型”的限制上。以下是具体对比:
| 类型 | 定义与本质 | 可赋值的类型(允许的值) | 禁止赋值的类型 | 典型场景与注意事项 |
|---|---|---|---|---|
Object |
首字母大写,对应JavaScript中原生的Object构造函数类型,代表“所有继承自Object.prototype的类型”(包括原始类型的包装对象)。 |
- 所有对象类型(普通对象、数组、函数等) - 所有原始类型( number、string、boolean等,因为它们会被“装箱”为包装对象,如Number、String)- null和undefined在非严格模式下可赋值(严格模式下禁止)。 |
无(几乎所有值都能赋值,因为原始类型会被隐式装箱为对象) | 过于宽泛,几乎失去类型约束意义,不推荐使用。除非明确需要兼容所有“继承自Object”的类型。 |
object |
小写,TypeScript 2.2新增,代表“非原始类型”(即排除所有原始类型的值)。 | - 所有对象类型(普通对象{}、数组[]、函数() => {}、日期Date等)- 不包括原始类型的包装对象(如 new Number(123)本质是对象,可赋值)。 |
- 所有原始类型(number、string、boolean、symbol、bigint)- null和undefined(严格模式下)。 |
用于明确限制“必须是非原始类型”的场景,比Object更严格,推荐使用(如函数参数需传入对象而非原始值时)。 |
{} |
空对象类型,代表“没有自有属性,但可继承Object.prototype方法的对象”(如toString、hasOwnProperty等)。 |
- 空对象{}- 所有原始类型(同 Object,因装箱后有继承的方法)- 非空对象(但访问其自有属性会报错,因为 {}未声明这些属性)。 |
- null和undefined(严格模式下)。 |
表示“一个空对象”,但允许原始类型赋值(因装箱特性)。不推荐直接使用,如需空对象建议用更严格的Record<string, never>。 |
关键区别示例:
// 1. Object 类型:允许原始类型和对象类型
const a: Object = 123; // 合法(123被装箱为Number对象)
const b: Object = 'hello'; // 合法(装箱为String对象)
const c: Object = { name: 'Tom' }; // 合法
const d: Object = () => {}; // 合法
// 2. object 类型:仅允许非原始类型(对象、数组、函数等)
const e: object = 123; // 报错(123是原始类型)
const f: object = 'hello'; // 报错(原始类型)
const g: object = { name: 'Tom' }; // 合法
const h: object = [1, 2, 3]; // 合法(数组是非原始类型)
const i: object = () => {}; // 合法(函数是非原始类型)
// 3. {} 类型:允许原始类型(装箱后)和空对象,但访问自有属性会报错
const j: {} = 123; // 合法(装箱后有继承方法)
const k: {} = { name: 'Tom' }; // 合法(但访问k.name会报错,因为{}未声明name属性)
const l: {} = {}; // 合法(空对象)
l.age = 18; // 报错({}类型不允许添加自有属性)
总结:
Object:最宽泛,兼容所有原始类型(装箱后)和对象类型,几乎无约束,不推荐。object:严格限制为“非原始类型”,适合需要明确排除原始值的场景,推荐使用。{}:表示“无自有属性的对象”,但仍兼容原始类型(装箱后),约束较弱,不推荐直接使用(建议用更具体的类型)。
实际开发中,应优先使用具体的对象类型(如{ name: string }、number[])而非这三种宽泛类型,以提升类型安全性。
详解{ name: string }{ name: string } 属于 TypeScript 中的 对象字面量类型(Object Literal Type),是一种具体的对象类型,用于精确描述对象的结构(即对象应包含的属性及其对应类型)。
特点:
-
结构约束:明确规定对象必须包含
name属性,且该属性的类型必须是string。- 例如:
const user: { name: string } = { name: 'Alice' };是合法的(符合结构)。 - 若缺少
name属性(如{ age: 18 })或name类型不符(如{ name: 123 }),会触发类型错误。
- 例如:
-
扩展性:默认情况下,对象字面量类型是“精确匹配”的,不允许多余的未声明属性(除非开启
suppressExcessPropertyErrors配置,但不推荐)。
例如:const user: { name: string } = { name: 'Bob', age: 20 };会报错(age是多余属性)。 -
匿名与命名:
{ name: string }本身是匿名的对象字面量类型,可通过type或interface命名复用:type User = { name: string }; // 命名为 User 类型 interface IUser { name: string }; // 接口形式(功能类似,细微差异)
与其他对象类型的区别:
- 不同于宽泛的
object类型(泛指所有非原始类型),{ name: string }是精确的结构描述,提供更强的类型约束。 - 不同于
{}(空对象类型,允许原始类型装箱后赋值),{ name: string }严格限制对象必须包含指定属性。
简单说,{ name: string } 是对“具有 name 字符串属性的对象”的类型定义,是 TypeScript 中描述对象结构最常用的方式之一。
对象的声明
在TypeScript中,创建对象的声明方式主要围绕“类型约束”展开,既可以直接使用对象字面量,也可以通过类型定义(如接口、类型别名)复用结构,还可以通过类实例化等方式。以下是常见的声明方式及示例:
1. 对象字面量(直接声明,带或不带类型注解)
最基础的方式,直接定义对象并可选地添加类型注解(明确对象结构)。
- 无类型注解:TypeScript会自动推断对象类型(根据属性值)。
- 带类型注解:显式指定对象的属性及类型,强制约束结构。
// 1.1 无类型注解(自动推断)
const user = {
name: "Alice",
age: 25,
isStudent: false
};
// 推断类型:{ name: string; age: number; isStudent: boolean }
// 1.2 带类型注解(显式约束)
const user: {
name: string;
age: number;
isStudent?: boolean; // 可选属性(可省略)
} = {
name: "Bob",
age: 30
// isStudent 可省略(因是可选属性)
};
特点:适合简单、一次性的对象,无需复用类型时使用。
2. 通过接口(interface)声明
接口(interface)用于定义对象的“结构契约”,可复用且支持继承,适合描述复杂对象类型。
// 定义接口(描述对象结构)
interface User {
name: string;
age: number;
readonly id: string; // 只读属性(初始化后不可修改)
sayHello?: () => void; // 可选方法
}
// 使用接口声明对象
const user1: User = {
name: "Charlie",
age: 28,
id: "u123",
sayHello: () => console.log("Hello")
};
const user2: User = {
name: "Diana",
age: 22,
id: "u124"
// 可省略可选方法 sayHello
};
特点:
- 支持合并声明(同一接口多次定义会自动合并);
- 可被类实现(
class XXX implements User); - 适合团队协作中复用对象结构。
3. 通过类型别名(type)声明
类型别名(type)通过type关键字为对象类型命名,功能类似接口,但更灵活(可描述联合类型、交叉类型等)。
// 定义类型别名(描述对象结构)
type Product = {
id: number;
name: string;
price: number;
};
// 使用类型别名声明对象
const phone: Product = {
id: 1,
name: "Phone",
price: 5999
};
// 类型别名支持联合/交叉类型(接口不支持)
type CartItem = Product & { quantity: number }; // 交叉类型(合并Product和quantity)
const cartItem: CartItem = {
id: 1,
name: "Phone",
price: 5999,
quantity: 2
};
特点:
- 不可合并声明(重复定义会报错);
- 支持更复杂的类型组合(联合、交叉等);
- 适合一次性定义复杂对象结构。
4. 通过类(class)实例化对象
类(class)用于创建“具有相同属性和方法的对象模板”,通过new实例化对象,类的类型会自动约束实例结构。
// 定义类(包含属性和方法)
class Animal {
name: string; // 实例属性(需显式类型)
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
bark(): void {
console.log(`${this.name} is barking`);
}
}
// 实例化对象(自动符合类的类型)
const dog = new Animal("Buddy", 3);
dog.bark(); // "Buddy is barking"
特点:
- 包含属性和方法,适合需要“行为”的对象;
- 支持继承(
extends)和访问修饰符(public/private/protected)。
5. 索引签名(处理动态属性的对象)
当对象属性名不固定(动态生成)时,使用索引签名(字符串/数字索引)声明对象类型。
// 字符串索引签名:键为string,值为number(如字典)
type NumberDict = {
[key: string]: number;
};
const scores: NumberDict = {
math: 90,
english: 85,
// 动态添加的属性也必须符合类型
chinese: 95
};
// 数字索引签名:键为number,值为string(如数组-like对象)
type StringArray = {
[index: number]: string;
length: number; // 显式属性(与索引类型无关)
};
const arr: StringArray = {
0: "a",
1: "b",
length: 2
};
特点:适合属性名动态变化的场景(如字典、配置对象)。
6. 匿名类型与类型断言
对于临时对象,可直接使用匿名类型;若TypeScript推断类型不准确,可通过类型断言(as)手动指定。
// 匿名类型(直接在变量后写类型结构)
const config: {
env: "dev" | "prod";
port: number;
} = {
env: "dev",
port: 3000
};
// 类型断言(当类型推断不符合预期时)
const data: unknown = { id: 1, name: "Test" };
const item = data as { id: number; name: string }; // 断言为目标类型
特点:匿名类型适合临时场景;类型断言用于“告诉TS实际类型”(需确保正确性,否则有风险)。
还有let a = <InterfaceName>{} 这种 TypeScript 中的类型断言(Type Assertion) 声明方式。
具体解释:
<InterfaceName>{}中的<InterfaceName>是类型断言的语法(另类型>值 的形式),作用是告诉编译器“该空对象{}应被视为InterfaceName类型”,强制指定对象的类型。- 这本质上是“手动告诉告诉 TypeScript 我比你更了解这个值的类型”,用于手动修正 TypeScript 的类型推断(但不会改变值的实际类型,仅影响类型检查)。
示例说明:
假设有一个接口 User:
interface User {
name: string;
age: number;
}
使用 <User>{} 断言空对象为 User 类型:
// 类型断言:将空对象 {} 断言为 User 类型
let a = <User>{};
// 此时 a 被编译器视为 User 类型,可以安全地添加属性(若不断言,直接给 {} 添加属性会报错)
a.name = "Alice";
a.age = 25;
与其他方式的区别:
-
vs 类型注解(
let a: User = {}):
类型注解(:语法)是直接声明变量的类型约束,此时若赋值{}会报错(因为缺少name和age属性);
而类型断言(<User>{})是“强制通过类型检查”,告诉编译器“虽然现在是空对象,但后续会符合User结构”,暂时不会报错(但需确保后续补全属性,否则可能导致运行时问题)。 -
另一种断言语法(
as):
TypeScript 还支持as语法进行类型断言(与<类型>等价),更推荐在 JSX 中使用(避免与 JSX 标签冲突):let a = {} as User; // 与 <User>{} 效果完全一致
总结
不同声明方式的适用场景:
- 简单、一次性对象:对象字面量;
- 复用结构、团队协作:接口(
interface); - 复杂类型组合(联合/交叉):类型别名(
type); - 带行为(方法)的对象:类(
class); - 动态属性对象:索引签名;
- 临时对象或类型修正:匿名类型/类型断言。
根据对象的复杂度和复用需求选择合适的方式即可。
2.12 symbol
自ECMAScript 2015起,symbol成为了一种新的原生类型。过Symbol构造函数创建。
let sym1 = Symbol();
let sym2 = Symbol("key"); // 可选的字符串key
//Symbols是不可改变且唯一的。
let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
//像字符串一样,symbols也可以被用做对象属性的键
let sym = Symbol();
let obj = {
[sym]: "value"
};
console.log(obj[sym]); // "value"
二、 解构和展开
1. 解构
最简单的解构莫过于数组的解构赋值了:
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
// 变量交换
[first, second] = [second, first];
// 函数参数
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);
// 数组里使用...语法创建剩余变量
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
// 忽略不关心的尾随元素
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
// 忽略某些元素
let [, second, , fourth] = [1, 2, 3, 4];
// 对象解构
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o; // c是可忽略的
//无声明赋值
({ a, b } = { a: "baz", b: 101 }); // 注意,我们需要用括号将它括起来,因为Javascript通常会将以 { 起始的语句解析为一个块。
//对象里使用...语法创建剩余变量
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
// 属性重命名
let { a: newName1, b: newName2 } = o; // 这里冒号不是指示类型的。
// 等价于
let newName1 = o.a;
let newName2 = o.b;
// 如果你想指定它的类型, 仍然需要在其后写上完整的模式
let {a, b}: {a: string, b: number} = o;
// 模仿对象参数默认值
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject; // 反直觉。即使wholeObject 的 b 为 undefined ,a,b都会有值。注意,wholeObject.b还是undefined
}
// 对比基础类型默认值,必选参数(age)在前,默认值参数(name)在后
function introduce(age: number, name: string = "Anonymous") {
console.log(`I'm ${name}, ${age} years old.`);
}
// 解构用于函数声明
type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}


2. 展开
展开操作创建的一份浅拷贝
对象后面的属性会覆盖前面的属性。
// 展开数组
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5]; //[0, 1, 2, 3, 4, 5]
// 展开对象
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" }; // { food: "rich", price: "$$", ambiance: "noisy" }
let search = { food: "rich", ...defaults }; // { food: "spicy", price: "$$", ambiance: "noisy" }
// 丢失对象方法
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
对象展开还有其它一些意想不到的限制:
- 它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法.
- TypeScript编译器不允许展开泛型函数上的类型参数。 这个特性会在TypeScript的未来版本中考虑实现。
三、 类型断言
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。
//方式1
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length; // <string>someValue部分属于类型断言
//方式2
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
三、 接口
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
// 使用接口改写
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
1. 可选属性
interface SquareConfig {
color?: string; // 可选
width?: number; // 可选
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) { // if (config.clor) { // Error: Property 'clor' does not exist on type 'SquareConfig'
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
可选属性的好处之一是可以对可能存在的属性进行预定义,
好处之二是可以捕获引用了不存在的属性时的错误。
2. 只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
ypeScript具有ReadonlyArray类型,它与Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
a = ro as number[]; // ok,可以用类型断言重写
readonly vs const
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const,若做为属性则使用readonly。
3. 额外的属性检查
不用细究,如果想直接通过字面量赋值给函数参数,则需要严格遵守接口要求。如果想接口宽松一点,可在接口中通过增加[name: type1]: type2;定义额外变量说明。从后面索引类型可知,type1只能是数字和字符串类型
interface SquareConfig {
color?: string; // 对于额外的类型检查,参数可选和必选结果一样
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
let mySquare = createSquare({ colour: "red", width: 100 }); // error: 'colour' not expected in type 'SquareConfig'
// 通过变量中转,避开额外的属性检查
let tmp= { colour: "red", width: 100 };
let mySquare = createSquare(tmp); // ok
// 宽松接口
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any; // 添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性
}
4. 接口描述可索引的类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
// 可设置为只读
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型.
这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
在TypeScript中,索引签名(Index Signatures)用于描述通过索引访问对象属性时的类型约束(例如 obj[key] 或 arr[index])。它允许我们定义“未知属性名但已知属性类型”的对象结构,常见于动态属性场景(如字典、数组等)。
TypeScript支持两种索引签名:字符串索引签名和数字索引签名,二者的核心区别在于“索引的类型”,但因JavaScript的特性(数字索引会被隐式转为字符串),同时使用时需遵守严格的兼容性规则。
1. 字符串索引签名(String Index Signatures)
通过字符串类型的索引(如 obj["name"])访问对象属性时,约束属性的类型。
语法:{ [key: string]: 类型 },其中 key 是任意合法的标识符(仅作为占位符,可自定义,如 prop、index 等)。
作用:规定“所有通过字符串索引访问的属性”必须符合指定类型,包括对象的显式属性(如 obj.name 本质是 obj["name"] 的语法糖)。
示例:
// 字符串索引签名:所有属性值必须是 string 类型
interface StringDict {
[key: string]: string;
// 显式属性也必须符合索引签名类型(name 是 string,合法)
name: string;
}
const dict: StringDict = {
name: "Alice",
age: "25", // 合法:age 是字符串索引,值为 string
};
console.log(dict["name"]); // "Alice"(符合 string 类型)
console.log(dict.age); // "25"(等价于 dict["age"],合法)
注意:
- 显式声明的属性类型必须与字符串索引签名的类型一致(否则报错)。例如,若添加
age: number,会因number与string不兼容报错。 - 字符串索引签名适用于“键为字符串的对象”(如普通对象、字典)。
2. 数字索引签名(Numeric Index Signatures)
通过数字类型的索引(如 arr[0])访问对象属性时,约束属性的类型。
语法:{ [index: number]: 类型 },其中 index 是占位符(可自定义)。
作用:规定“所有通过数字索引访问的属性”必须符合指定类型,常见于数组、类数组对象(如 arguments)。
示例:
// 数字索引签名:所有数字索引访问的属性值必须是 number 类型
interface NumberArray {
[index: number]: number;
}
const arr: NumberArray = [1, 2, 3];
console.log(arr[0]); // 1(符合 number 类型)
arr[1] = 4; // 合法:数字索引赋值为 number
// 类数组对象(如 DOM 集合)
interface NodeListLike {
[index: number]: HTMLElement;
length: number; // 显式属性 length 是 number(与数字索引类型无关)
}
注意:
- 数字索引签名的“索引”本质是数字,但在JavaScript中,数字索引会被隐式转换为字符串(例如
arr[0]等价于arr["0"])。 - 显式属性(如数组的
length)的类型无需与数字索引类型一致(length是number,与索引值的number只是巧合,非强制)。
同时使用两种索引签名的注意事项
当一个对象同时声明字符串索引签名和数字索引签名时,需满足数字索引的返回类型必须是字符串索引返回类型的子类型。
这是因为:在JavaScript中,数字索引会被自动转为字符串索引(例如 obj[0] 等价于 obj["0"])。因此,obj[0] 的类型(数字索引返回类型)必须兼容 obj["0"] 的类型(字符串索引返回类型),否则会出现类型矛盾。
1. 正确示例(数字索引类型是字符串索引类型的子类型)
interface CompatibleIndex {
// 字符串索引:返回类型为 string | number(父类型)
[key: string]: string | number;
// 数字索引:返回类型为 number(是 string | number 的子类型,兼容)
[index: number]: number;
}
const obj: CompatibleIndex = {
"0": "hello", // 字符串索引 "0" 对应 string(符合 string | number)
0: 123, // 数字索引 0 对应 number(符合数字索引类型,且与 "0" 的 string 兼容)
name: "Alice", // 字符串索引 "name" 对应 string(合法)
age: 25, // 字符串索引 "age" 对应 number(合法)
};
console.log(obj[0]); // 123(数字索引,返回 number)
console.log(obj["0"]); // "hello"(字符串索引,返回 string)
// 二者类型分别为 number 和 string,均兼容 string | number,无矛盾
2. 错误示例(数字索引类型与字符串索引类型不兼容)
interface IncompatibleIndex {
[key: string]: string; // 字符串索引:返回类型为 string
[index: number]: number; // 错误!number 不是 string 的子类型
}
// TypeScript 报错:
// 数字索引类型 'number' 不能赋给字符串索引类型 'string'
原因:obj[0](数字索引)返回 number,但它等价于 obj["0"](字符串索引),而字符串索引要求返回 string,导致 number 与 string 冲突,因此不允许。
3. 其他注意事项
-
显式属性需兼容字符串索引:若对象有显式属性(如
name: string),其类型必须与字符串索引签名的类型一致(与数字索引无关)。
例如:interface Example { [key: string]: string; [index: number]: string; // 数字索引是 string 的子类型(兼容) length: number; // 错误!length 的类型 number 与字符串索引的 string 不兼容 } -
索引签名的“只读”修饰符:可通过
readonly限制索引签名为只读(不能通过索引赋值),两种索引签名可分别设置只读:interface ReadonlyIndex { readonly [key: string]: string; // 字符串索引只读 readonly [index: number]: string; // 数字索引只读 } const obj: ReadonlyIndex = { "0": "a", 0: "b" }; obj[0] = "c"; // 错误:只读索引不能赋值
5. 接口描述函数类型
描述带有属性的普通对象外,接口也可以描述函数类型。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean { // ok,函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配
let result = src.search(sub);
return result > -1;
}
mySearch = function(src, sub) {// ok, 如果你不想指定类型,TypeScript的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc类型变量
let result = src.search(sub);
return result > -1;
}
6. 接口描述类类型
TypeScript也能够用接口来明确的强制一个类去符合某种契约。
接口只类的公共部分, 它不会帮你检查类是否具有某些私有成员。
interface ClockInterface {
currentTime: Date; // 属性
setTime(d: Date); // 方法,需在类里实现它
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
类静态部分与实例部分的区别
当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。当用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误。
// 报错
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
// 应该直接操作静态部分
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
7. 接口继承接口
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
// 继承多个接口
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
8. 混合类型
有时希望一个对象可以同时具有多种类型。
一个例子就是,一个对象可以同时做为函数和对象使用,并带有额外的属性。
interface Counter {
(start: number): string; // todo: 待解释
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
9. 接口继承类
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。
就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。
接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现。
当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
class Location {
}
参考
-----TypeScript官方教程-----
-----ArkTS详细教程------
TypeScript官方教程(英文)
TypeScript 基础语法及使用
TypeScript 基本语法
TypeScript基础语法(详细版)
【前端知识】TypeScript语法介绍
更多推荐


所有评论(0)