📚 TypeScript 教程系列

  1. 入门与配置
  2. 基础类型与变量声明
  3. 函数
  4. 流程控制与运算符
  5. 集合类型(本文)
  6. 异步编程与错误处理
  7. 接口与类
  8. 泛型与类型组合
  9. 高级类型
  10. 模块、装饰器与工程化

⚠️ 来源声明:本文内容参考自 菜鸟教程 TypeScript 教程,仅供学习交流,版权归原作者所有。
TypeScript 在 JavaScript 的基础上为各类集合类型加上了静态类型约束,使得数组、元组、枚举、Map、Set 以及对象在编写阶段就能获得类型检查与智能提示。本篇将这几种常用的集合类型串联起来,从声明、访问到常用方法逐一展开,帮助你建立完整的类型化数据结构心智模型。

数组(Array)

数组对象用于使用单独的变量名来存储一系列的值。例如,将多个网站名字各自存入单独变量会非常繁琐,而使用数组则简洁得多:

1
2
var sites:string[];
sites = ["Google","Runoob","Taobao"]

声明数组

TypeScript 声明数组的语法格式如下:

1
2
var array_name[:datatype];        // 声明
array_name = [val1,val2,valn..] // 初始化

或者直接在声明时初始化:

1
var array_name[:datatype] = [val1,val2…valn]

若声明数组时未指定类型,则默认视为 any 类型,初始化时会根据第一个元素的类型来推断数组的类型。

数组声明与初始化

创建一个 number 类型的数组:

1
var numlist:number[] = [2,4,6,8]

索引值从 0 开始,可通过索引访问元素:

1
2
3
4
var sites:string[];
sites = ["Google","Runoob","Taobao"]
console.log(sites[0]);
console.log(sites[1]);

输出:

1
2
Google
Runoob

声明时直接初始化的示例:

1
2
3
4
5
var nums:number[] = [1,2,3,4]
console.log(nums[0]);
console.log(nums[1]);
console.log(nums[2]);
console.log(nums[3]);

输出:

1
2
3
4
1
2
3
4

Array 对象

也可以使用 Array 对象来创建数组。其构造函数接受以下两种值:

  • 表示数组大小的数值
  • 初始化的数组列表(逗号分隔)

实例:指定数组初始化大小:

1
2
3
4
5
var arr_names:number[] = new Array(4)
for(var i = 0; i<arr_names.length; i++) {
arr_names[i] = i * 2
console.log(arr_names[i])
}

输出:

1
2
3
4
0
2
4
6

实例:直接初始化数组元素:

1
2
3
4
var sites:string[] = new Array("Google","Runoob","Taobao","Facebook")
for(var i = 0;i<sites.length;i++) {
console.log(sites[i])
}

输出:

1
2
3
4
Google
Runoob
Taobao
Facebook

数组解构

可将数组元素赋值给变量:

1
2
3
4
var arr:number[] = [12,13]
var[x,y] = arr // 将数组的两个元素赋值给变量 x 和 y
console.log(x)
console.log(y)

输出:

1
2
12
13

数组迭代

使用 for...in 语句循环输出数组元素:

1
2
3
4
5
var j:any;
var nums:number[] = [1001,1002,1003,1004]
for(j in nums) {
console.log(nums[j])
}

输出:

1
2
3
4
1001
1002
1003
1004

多维数组

一个数组的元素可以是另外一个数组,这样就构成了多维数组。最简单的二维数组定义方式:

1
var arr_name:datatype[][]=[ [val1,val2,val3],[v1,v2,v3] ]

多维数组

实例:

1
2
3
4
5
6
7
var multi:number[][] = [[1,2,3],[23,24,25]]
console.log(multi[0][0])
console.log(multi[0][1])
console.log(multi[0][2])
console.log(multi[1][0])
console.log(multi[1][1])
console.log(multi[1][2])

输出:

1
2
3
4
5
6
1
2
3
23
24
25

数组在函数中的使用

作为参数传递给函数:

1
2
3
4
5
6
7
var sites:string[] = new Array("Google","Runoob","Taobao","Facebook")
function disp(arr_sites:string[]) {
for(var i = 0;i<arr_sites.length;i++) {
console.log(arr_sites[i])
}
}
disp(sites);

输出:

1
2
3
4
Google
Runoob
Taobao
Facebook

作为函数的返回值:

1
2
3
4
5
6
7
function disp():string[] {
return new Array("Google", "Runoob", "Taobao", "Facebook");
}
var sites:string[] = disp()
for(var i in sites) {
console.log(sites[i])
}

输出:

1
2
3
4
Google
Runoob
Taobao
Facebook

数组方法

下表列出了一些常用的数组方法:

序号方法 & 描述实例
1.concat() — 连接两个或更多的数组,返回结果var alpha = ["a", "b", "c"]; var numeric = [1, 2, 3]; var alphaNumeric = alpha.concat(numeric); → a,b,c,1,2,3
2.every() — 检测每个元素是否都符合条件function isBigEnough(element, index, array) { return (element >= 10); } var passed = [12, 5, 8, 130, 44].every(isBigEnough); → false
3.filter() — 返回符合条件所有元素的数组var passed = [12, 5, 8, 130, 44].filter(isBigEnough); → 12,130,44
4.forEach() — 每个元素执行一次回调函数let num = [7, 8, 9]; num.forEach(function (value) { console.log(value); }); → 7 8 9
5.indexOf() — 搜索元素并返回位置,未找到返回 -1var index = [12, 5, 8, 130, 44].indexOf(8); → 2
6.join() — 将所有元素放入一个字符串var arr = new Array("Google","Runoob","Taobao"); var str = arr.join(); → Google,Runoob,Taobao;arr.join(" + ") → Google + Runoob + Taobao
7.lastIndexOf() — 返回指定值最后出现的位置,从后向前搜索var index = [12, 5, 8, 130, 44].lastIndexOf(8); → 2
8.map() — 通过指定函数处理每个元素,返回处理后的数组var numbers = [1, 4, 9]; var roots = numbers.map(Math.sqrt); → 1,2,3
9.pop() — 删除最后一个元素并返回它var numbers = [1, 4, 9]; var element = numbers.pop(); → 9
10.push() — 向末尾添加一个或多个元素,返回新长度var length = numbers.push(10); → 1,4,9,10
11.reduce() — 将数组元素计算为一个值(从左到右)var total = [0, 1, 2, 3].reduce(function(a, b){ return a + b; }); → 6
12.reduceRight() — 将数组元素计算为一个值(从右到左)var total = [0, 1, 2, 3].reduceRight(function(a, b){ return a + b; }); → 6
13.reverse() — 反转数组元素顺序var arr = [0, 1, 2, 3].reverse(); → 3,2,1,0
14.shift() — 删除并返回第一个元素var arr = [10, 1, 2, 3].shift(); → 10
15.slice() — 选取一部分并返回新数组var arr = ["orange", "mango", "banana", "sugar", "tea"]; arr.slice( 1, 2); → mango
16.some() — 检测是否有元素符合条件var retval = [2, 5, 8, 1, 4].some(isBigEnough); → false;改为 [12, 5, 8, 1, 4] → true
17.sort() — 对元素排序var sorted = arr.sort(); → banana,mango,orange,sugar
18.splice() — 从数组中添加或删除元素var removed = arr.splice(2, 0, "water"); 插入 water
19.toString() — 将数组转换为字符串var str = arr.toString(); → orange,mango,banana,sugar
20.unshift() — 向开头添加一个或多个元素,返回新长度var length = arr.unshift("water"); → water,orange,mango,banana,sugar

元组(Tuple)

我们知道数组中元素的数据类型一般相同(any[] 类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组。元组是一种特殊数组,允许存储不同类型的元素,每个元素都有明确的类型和位置,适合表示固定长度且各元素类型已知的数据结构。

创建元组

语法格式:

1
let tuple: [类型1, 类型2, 类型3, ...];

实例:

1
2
let mytuple: [number, string];
mytuple = [42, "Runoob"];

访问元组

通过索引访问,索引从 0 开始,语法:tuple_name[index]

1
2
3
4
5
6
7
let mytuple: [number, string, boolean] = [42, "Runoob", true];
let num = mytuple[0];
let str = mytuple[1];
let bool = mytuple[2];
console.log(num);
console.log(str);
console.log(bool);

输出:

1
2
3
42
Runoob
true

元组运算

  • push():向元组末尾添加元素
  • pop():从元组末尾移除元素并返回该元素

push 实例:

1
2
3
var tuple = [42, "Hello"];
tuple.push("World");
console.log(tuple); // 输出: [42, "Hello", "World"]

pop 实例:

1
2
3
4
let tuple: [number, string, boolean] = [42, "Hello", true];
let lastElement = tuple.pop();
console.log(lastElement); // 输出: true
console.log(tuple); // 输出: [42, "Hello"]

更新元组

元组是可变的,可通过索引直接赋值更新。

1
2
3
4
var mytuple = [42, "Runoob", "Taobao", "Google"];
console.log("元组的第一个元素为:" + mytuple[0])
mytuple[0] = 121
console.log("元组中的第一个元素更新为:" + mytuple[0])

输出:

1
2
元组的第一个元素为:42
元组中的第一个元素更新为:121

解构元组

可将元组元素赋值给变量:

1
2
3
4
let a: [number, string, boolean] = [42, "Hello", true];
var [b,c] = a
console.log(b)
console.log(c)

输出:

1
2
42
Hello

标签元组与其他操作

可为元组元素添加标签以提高可读性:

1
let tuple: [id: number, name: string] = [1, "John"];

元组常用于函数返回多个值的场景:

1
2
3
4
5
6
function getUserInfo(): [number, string] {
return [1, "John Doe"];
}
const [userId, userName] = getUserInfo();
console.log(userId); // 输出: 1
console.log(userName); // 输出: John Doe

使用 as const 将元组视为不可变常量元组:

1
let tuple = [42, "Hello"] as const; // 元组类型:[42, "Hello"]

使用 concat 方法连接元组,结果是一个普通数组而非元组:

1
2
3
4
let tuple1: [number, string] = [42, "Hello"];
let tuple2: [boolean, number] = [true, 100];
let result = tuple1.concat(tuple2);
console.log(result); // 输出: [42, "Hello", true, 100]

使用 slice 方法切片元组,返回新数组:

1
2
3
let tuple: [number, string, boolean] = [42, "Hello", true];
let sliced = tuple.slice(1);
console.log(sliced); // 输出: ["Hello", true]

遍历元组可使用 for...of 循环或 forEach 方法:

1
2
3
4
5
let tuple: [number, string, boolean] = [42, "Hello", true];
for (let item of tuple) {
console.log(item);
}
tuple.forEach(item => console.log(item));

通过 Array.from 转换为普通数组后可使用数组方法:

1
2
3
4
let tuple: [number, string, boolean] = [42, "Hello", true];
let array = Array.from(tuple);
array.push("New Element");
console.log(array); // 输出: [42, "Hello", true, "New Element"]

使用剩余运算符合并多个元组:

1
2
3
4
let tuple1: [number, string] = [42, "Hello"];
let tuple2: [boolean] = [true];
let extendedTuple: [number, string, ...typeof tuple2] = [42, "Hello", ...tuple2];
console.log(extendedTuple); // 输出: [42, "Hello", true]

枚举(Enum)

枚举(Enum)类型用于定义数值集合,让开发者为一组数值赋予友好的名字,从而避免在代码中出现"魔术数字",提升可读性与可维护性。

数字枚举

默认情况下,枚举成员从 0 开始编号:

1
2
3
4
5
6
7
8
9
10
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}

var dir: Direction = Direction.Up;
console.log("方向: " + dir);
console.log("方向名称: " + Direction[0]);

输出:

1
2
方向: 0
方向名称: Up

手动赋值

可以手动为枚举成员指定值:

1
2
3
4
5
6
7
8
enum Status {
Success = 1,
Error = 2,
Pending = 3
}

console.log("状态: " + Status.Success);
console.log("状态名称: " + Status[1]);

输出:

1
2
状态: 1
状态名称: Success

字符串枚举

字符串枚举要求每个成员都必须赋予字符串字面量值:

1
2
3
4
5
6
7
enum Message {
Success = "SUCCESS",
Error = "ERROR",
Warning = "WARNING"
}

console.log("消息: " + Message.Success);

输出:

1
消息: SUCCESS

常量枚举

使用 const 修饰符可以声明常量枚举,它会在编译时被内联,从而生成更优化的代码:

1
2
3
4
5
6
7
8
const enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}

var favoriteColor: Color = Color.Red;
console.log("喜欢的颜色: " + favoriteColor);

异构枚举

枚举可以混合数字和字符串值,但这种用法并不推荐:

1
2
3
4
5
6
7
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES"
}

console.log("值: " + BooleanLikeHeterogeneousEnum.No);
console.log("字符串值: " + BooleanLikeHeterogeneousEnum.Yes);

枚举成员类型

当所有成员都是字面量值时,单个成员可以作为类型使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enum ShapeKind {
Circle = "circle",
Square = "square"
}

interface Circle {
kind: ShapeKind.Circle;
radius: number;
}

interface Square {
kind: ShapeKind.Square;
sideLength: number;
}

var c: Circle = {
kind: ShapeKind.Circle,
radius: 10
};

console.log("圆形: " + JSON.stringify(c));

运行时常量枚举

普通枚举在运行时会作为真实对象保留:

1
2
3
4
5
6
7
enum FileAccess {
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write
}

console.log("文件访问: " + FileAccess.ReadWrite);

输出:

1
文件访问: 6

枚举类型小结:

  • 数字枚举:默认从 0 开始,支持手动赋值
  • 字符串枚举:每个成员必须是字符串字面量
  • 常量枚举:使用 const,编译时内联
  • 异构枚举:混合数字与字符串(不推荐)
  • 成员类型:字面量成员可作为类型使用

Map 对象

Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。Map 是 ES6 中引入的新特性。

创建 Map

TypeScript 使用 Map 类型与 new 关键字来创建 Map:

1
let myMap = new Map();

初始化时可以传入键值对数组:

1
2
3
4
let myMap = new Map([
["key1", "value1"],
["key2", "value2"]
]);

Map 的属性与方法

方法 / 属性说明
map.clear()移除所有键值对
map.set(key, value)设置键值对,返回 Map 本身
map.get(key)返回键对应的值,不存在则返回 undefined
map.has(key)判断是否包含该键,返回布尔值
map.delete(key)删除键值对,返回 true/false
map.size返回键值对数量
map.keys()返回键的迭代器
map.values()返回值的迭代器
map.entries()返回所有 [key, value] 的迭代器

常用函数签名如下:

  • set(key: K, value: V): this — 添加或更新键值对
  • get(key: K): V | undefined — 根据键取值
  • has(key: K): boolean — 判断键是否存在
  • delete(key: K): boolean — 移除键值对
  • clear(): void — 清空所有条目
  • size: number — 条目数量
  • keys(): IterableIterator<K> — 返回键的迭代器
  • values(): IterableIterator<V> — 返回值的迭代器
  • entries(): IterableIterator<[K, V]> — 返回 [key, value] 数组的迭代器
  • forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void — 对每个条目执行回调

带类型参数的 Map 示例:

1
2
3
4
5
6
7
8
9
10
const map = new Map<string, number>();
map.set('one', 1);
map.set('two', 2);
console.log(map.get('one')); // 1
console.log(map.has('two')); // true
map.delete('one');
console.log(map.size); // 1
map.forEach((value, key) => { console.log(key, value); }); // two 2
map.clear();
console.log(map.size); // 0

完整示例(test.ts),创建一个字符串键、数字值的 nameSiteMapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let nameSiteMapping = new Map<string, number>();

// 设置键值对
nameSiteMapping.set("Google", 1);
nameSiteMapping.set("Runoob", 2);
nameSiteMapping.set("Taobao", 3);

// 获取键对应的值
console.log(nameSiteMapping.get("Runoob")); // 2
// 判断 Map 中是否包含某键
console.log(nameSiteMapping.has("Taobao")); // true
console.log(nameSiteMapping.has("Zhihu")); // false
// 返回 Map 对象键/值对的数量
console.log(nameSiteMapping.size); // 3
// 删除某键
console.log(nameSiteMapping.delete("Runoob")); // true
console.log(nameSiteMapping); // Map { 'Google' => 1, 'Taobao' => 3 }
// 移除所有键值对,清空 Map
nameSiteMapping.clear();
console.log(nameSiteMapping); // Map {}

使用 tsc --target es6 test.ts 编译后运行,输出:

1
2
3
4
5
6
7
2
true
false
3
true
Map { 'Google' => 1, 'Taobao' => 3 }
Map {}

迭代 Map

Map 中的元素按插入顺序排列,TypeScript 使用 for...of 来迭代:

1
2
3
4
5
6
7
8
9
10
11
12
for (let key of nameSiteMapping.keys()) {
console.log(key);
}
for (let value of nameSiteMapping.values()) {
console.log(value);
}
for (let entry of nameSiteMapping.entries()) {
console.log(entry[0], entry[1]);
}
for (let [key, value] of nameSiteMapping) {
console.log(key, value);
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
Google
Runoob
Taobao
1
2
3
Google 1
Runoob 2
Taobao 3
Google 1
Runoob 2
Taobao 3

Map 的键可以是任意类型(对象、函数等),比使用普通对象作为键更灵活,是实现缓存、统计、索引等功能的理想选择。

Set 与 WeakMap/WeakSet

TypeScript 继承自 JavaScript 的 Set 和 WeakMap 数据结构,提供了更强大的类型支持。这些数据结构在处理唯一值集合、键值对映射、缓存等场景非常有用。

数据结构对比

数据结构特点行为方法
Set值的集合值唯一,不重复,可遍历add/has/delete
WeakSet对象弱引用不影响 GC,不可遍历add/has/delete
Map键值对集合键可以是任意类型,可遍历set/get/has
WeakMap键弱引用键必须是对象,不影响 GC,不可遍历set/get/has

应用场景:Set 用于去重、唯一值集合;Map 用于键值映射、缓存;WeakMap/WeakSet 用于内存优化。

Set 提供了自动去重的集合功能,比数组更方便处理唯一值;WeakSet 和 WeakMap 使用弱引用,不会阻止垃圾回收,适用于避免内存泄漏的场景,如缓存 DOM 节点。

Set

Set 是值的集合,值唯一,不允许重复。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建 Set,指定元素类型为 number
var numbers = new Set<number>();

// 添加元素
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(1); // 重复值会被忽略,不会添加

// 检查大小和包含
console.log("Set 大小: " + numbers.size);
console.log("是否包含 2: " + numbers.has(2));

// 遍历 Set
numbers.forEach(function(value) {
console.log("值: " + value);
});

// 转换为数组
var arr = Array.from(numbers);
console.log("转换为数组: " + arr);

运行结果:

1
2
3
4
5
6
Set 大小: 3
是否包含 2: true
: 1
: 2
: 3
转换为数组: 1,2,3

Set 会自动忽略重复的值,这使得它非常适合用于数组去重。

Set 类型注解

可以显式指定 Set 中值的类型,使用 Set<T> 语法指定 Set 中元素的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 字符串 Set
// 只能添加字符串类型的值
var stringSet: Set<string> = new Set();
stringSet.add("a");
stringSet.add("b");

// 对象 Set
// 定义 Person 接口
interface Person {
name: string;
}
// 创建存储 Person 对象的 Set
var personSet: Set<Person> = new Set();
personSet.add({ name: "Alice" });
personSet.add({ name: "Bob" });

console.log("字符串 Set: " + Array.from(stringSet));
console.log("对象 Set 大小: " + personSet.size);

WeakSet

WeakSet 存储对象引用,引用为弱引用(不影响垃圾回收)。WeakSet 只能存储对象,不能存储原始值,且不能遍历,类型注解只能是 object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// WeakSet 只能存储对象,不能存储原始值
var weakSet = new WeakSet();

// 创建对象
var obj1 = { name: "Alice" };
var obj2 = { name: "Bob" };

// 添加对象到 WeakSet
weakSet.add(obj1);
weakSet.add(obj2);

// 检查是否包含
console.log("是否包含 obj1: " + weakSet.has(obj1));

// 移除引用后,对象可能被垃圾回收
weakSet.delete(obj1);
console.log("删除后是否包含 obj1: " + weakSet.has(obj1));

WeakSet 适合存储需要被垃圾回收的对象。

WeakMap

WeakMap 的键是弱引用,不影响垃圾回收。WeakMap 的键必须是对象,不可遍历,常用于缓存 DOM 节点数据——当 DOM 节点被移除时,缓存数据也会被自动清理,避免内存泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// WeakMap 的键必须是对象
// 键类型为 object,值类型为 string
var weakMap = new WeakMap<object, string>();

// 创建对象作为键
var keyObj = { id: 1 };
// 设置键值对
weakMap.set(keyObj, "value1");

// 获取值
console.log("获取值: " + weakMap.get(keyObj));
console.log("是否包含: " + weakMap.has(keyObj));

// 删除键值对
weakMap.delete(keyObj);
console.log("删除后: " + weakMap.has(keyObj));

实际应用场景

使用 Map 统计数组元素出现次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 使用 Map 统计数组中每个元素的出现次数
function countElements(arr: string[]): Map<string, number> {
// 创建 Map,键是字符串,值是数字
var counts = new Map<string, number>();

// 遍历数组
for (var _i = 0, arr_1 = arr; _i < arr_1.length; _i++) {
var item = arr_1[_i];
// 获取当前计数,如果没有则返回 0
var currentCount = counts.get(item) || 0;
// 更新计数
counts.set(item, currentCount + 1);
}

return counts;
}

// 测试
var fruits = ["apple", "banana", "apple", "orange", "banana", "apple"];
var result = countElements(fruits);

// 遍历结果
result.forEach(function(count, fruit) {
console.log(fruit + ": " + count);
});

运行结果:

1
2
3
apple: 3
banana: 2
orange: 1

注意事项

  • Set 唯一性:Set 自动忽略重复值
  • WeakSet/WeakMap:键必须是对象,不能遍历
  • Map 键类型:Map 的键可以是任意类型
  • 内存管理:WeakSet/WeakMap 不阻止垃圾回收

选择建议:需要唯一值集合用 Set,需要键值映射用 Map,需要避免内存泄漏用 WeakSet/WeakMap。

对象(Object)

对象是包含一组键值对的实例。值可以是标量、函数、数组、对象等。

对象语法模板

1
2
3
4
5
6
7
var object_name = {
key1: "value1", // 标量
key2: "value",
key3: function() { // 函数
},
key4:["content1", "content2"] // 集合
}

对象实例:

1
2
3
4
5
6
7
var sites = {
site1:"Runoob",
site2:"Google"
};
// 访问对象的值
console.log(sites.site1)
console.log(sites.site2)

输出:

1
2
Runoob
Google

TypeScript 类型模板

在 JavaScript 中定义对象后可以动态添加方法,但 TypeScript 中的对象必须是特定类型的实例,因此需要预先在类型模板中声明方法。

1
2
3
4
5
6
7
8
9
var sites = {
site1: "Runoob",
site2: "Google",
sayHello: function () { } // 类型模板
};
sites.sayHello = function () {
console.log("hello " + sites.site1);
};
sites.sayHello();

输出:

1
hello Runoob

对象作为参数传递给函数

1
2
3
4
5
6
7
8
9
var sites = {
site1:"Runoob",
site2:"Google",
};
var invokesites = function(obj: { site1:string, site2 :string }) {
console.log("site1 :"+obj.site1)
console.log("site2 :"+obj.site2)
}
invokesites(sites)

输出:

1
2
site1 :Runoob
site2 :Google

鸭子类型(Duck Typing)

鸭子类型是动态类型的一种风格,是多态的一种形式。在这种风格中,一个对象有效的语义不是由继承自特定的类或实现特定的接口决定,而是由当前方法和属性的集合决定。

核心表述:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。

在鸭子类型中,关注点在于对象的行为能做什么,而不是关注对象所属的类型。在不使用鸭子类型的语言中,可以编写一个函数接受类型为"鸭子"的对象并调用其"走"和"叫"方法;在使用鸭子类型的语言中,这样的函数可以接受任意类型的对象并调用其"走"和"叫"方法。如果这些方法不存在,将引发运行时错误。

TypeScript 接口与鸭子类型示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IPoint {
x:number
y:number
}
function addPoints(p1:IPoint,p2:IPoint):IPoint {
var x = p1.x + p2.x
var y = p1.y + p2.y
return {x:x,y:y}
}
// 正确
var newPoint = addPoints({x:3,y:4},{x:5,y:1})
// 错误
var newPoint2 = addPoints({x:1},{x:4,y:3})

只要传入的对象具备接口所要求的 xy 属性,TypeScript 就认为它符合 IPoint 类型,这正是鸭子类型在静态类型系统中的体现。