📚 TypeScript 教程系列
入门与配置 基础类型与变量声明 函数 流程控制与运算符 集合类型 (本文)异步编程与错误处理 接口与类 泛型与类型组合 高级类型 模块、装饰器与工程化 ⚠️ 来源声明 :本文内容参考自 菜鸟教程 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 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 ]);
输出:
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 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 console .log (x)console .log (y)
输出:
数组迭代 使用 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 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 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,32. every() — 检测每个元素是否都符合条件function isBigEnough(element, index, array) { return (element >= 10); } var passed = [12, 5, 8, 130, 44].every(isBigEnough); → false3. filter() — 返回符合条件所有元素的数组var passed = [12, 5, 8, 130, 44].filter(isBigEnough); → 12,130,444. forEach() — 每个元素执行一次回调函数let num = [7, 8, 9]; num.forEach(function (value) { console.log(value); }); → 7 8 95. indexOf() — 搜索元素并返回位置,未找到返回 -1var index = [12, 5, 8, 130, 44].indexOf(8); → 26. join() — 将所有元素放入一个字符串var arr = new Array("Google","Runoob","Taobao"); var str = arr.join(); → Google,Runoob,Taobao;arr.join(" + ") → Google + Runoob + Taobao7. lastIndexOf() — 返回指定值最后出现的位置,从后向前搜索var index = [12, 5, 8, 130, 44].lastIndexOf(8); → 28. map() — 通过指定函数处理每个元素,返回处理后的数组var numbers = [1, 4, 9]; var roots = numbers.map(Math.sqrt); → 1,2,39. pop() — 删除最后一个元素并返回它var numbers = [1, 4, 9]; var element = numbers.pop(); → 910. push() — 向末尾添加一个或多个元素,返回新长度var length = numbers.push(10); → 1,4,9,1011. reduce() — 将数组元素计算为一个值(从左到右)var total = [0, 1, 2, 3].reduce(function(a, b){ return a + b; }); → 612. reduceRight() — 将数组元素计算为一个值(从右到左)var total = [0, 1, 2, 3].reduceRight(function(a, b){ return a + b; }); → 613. reverse() — 反转数组元素顺序var arr = [0, 1, 2, 3].reverse(); → 3,2,1,014. shift() — 删除并返回第一个元素var arr = [10, 1, 2, 3].shift(); → 1015. slice() — 选取一部分并返回新数组var arr = ["orange", "mango", "banana", "sugar", "tea"]; arr.slice( 1, 2); → mango16. some() — 检测是否有元素符合条件var retval = [2, 5, 8, 1, 4].some(isBigEnough); → false;改为 [12, 5, 8, 1, 4] → true17. sort() — 对元素排序var sorted = arr.sort(); → banana,mango,orange,sugar18. splice() — 从数组中添加或删除元素var removed = arr.splice(2, 0, "water"); 插入 water19. toString() — 将数组转换为字符串var str = arr.toString(); → orange,mango,banana,sugar20. 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);
输出:
元组运算 push() :向元组末尾添加元素pop() :从元组末尾移除元素并返回该元素push 实例:
1 2 3 var tuple = [42 , "Hello" ]; tuple.push ("World" );console .log (tuple);
pop 实例:
1 2 3 4 let tuple : [number , string , boolean ] = [42 , "Hello" , true ];let lastElement = tuple.pop ();console .log (lastElement); console .log (tuple);
更新元组 元组是可变的,可通过索引直接赋值更新。
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] = aconsole .log (b)console .log (c)
输出:
标签元组与其他操作 可为元组元素添加标签以提高可读性:
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); console .log (userName);
使用 as const 将元组视为不可变常量元组:
1 let tuple = [42 , "Hello" ] as const ;
使用 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);
使用 slice 方法切片元组,返回新数组:
1 2 3 let tuple : [number , string , boolean ] = [42 , "Hello" , true ];let sliced = tuple.slice (1 );console .log (sliced);
遍历元组可使用 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);
使用剩余运算符合并多个元组:
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);
枚举(Enum) 枚举(Enum)类型用于定义数值集合,让开发者为一组数值赋予友好的名字,从而避免在代码中出现"魔术数字",提升可读性与可维护性。
数字枚举 默认情况下,枚举成员从 0 开始编号:
1 2 3 4 5 6 7 8 9 10 enum Direction { Up , Down , Left , Right }var dir : Direction = Direction .Up ;console .log ("方向: " + dir);console .log ("方向名称: " + Direction [0 ]);
输出:
手动赋值 可以手动为枚举成员指定值:
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 3 4 5 6 7 enum Message { Success = "SUCCESS" , Error = "ERROR" , Warning = "WARNING" }console .log ("消息: " + Message .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 );
输出:
枚举类型小结:
数字枚举 :默认从 0 开始,支持手动赋值字符串枚举 :每个成员必须是字符串字面量常量枚举 :使用 const,编译时内联异构枚举 :混合数字与字符串(不推荐)成员类型 :字面量成员可作为类型使用Map 对象 Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值)都可以作为一个键或一个值。Map 是 ES6 中引入的新特性。
创建 Map TypeScript 使用 Map 类型与 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' )); console .log (map.has ('two' )); map.delete ('one' );console .log (map.size ); map.forEach ((value, key ) => { console .log (key, value); }); map.clear ();console .log (map.size );
完整示例(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" )); console .log (nameSiteMapping.has ("Taobao" )); console .log (nameSiteMapping.has ("Zhihu" )); console .log (nameSiteMapping.size ); console .log (nameSiteMapping.delete ("Runoob" )); console .log (nameSiteMapping); nameSiteMapping.clear ();console .log (nameSiteMapping);
使用 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 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 )); 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 var stringSet : Set <string > = new Set (); stringSet.add ("a" ); stringSet.add ("b" );interface Person { name : string ; }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 var weakSet = new WeakSet ();var obj1 = { name : "Alice" };var obj2 = { name : "Bob" }; 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 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 function countElements (arr : string [] ): Map <string , number > { var counts = new Map <string , number >(); for (var _i = 0 , arr_1 = arr; _i < arr_1.length ; _i++) { var item = arr_1[_i]; 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 )
输出:
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 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 })
只要传入的对象具备接口所要求的 x 和 y 属性,TypeScript 就认为它符合 IPoint 类型,这正是鸭子类型在静态类型系统中的体现。