21-Symbol、Map 与 Set Symbol、Map 与 Set三种 ES6 新增的数据类型分别解决「唯一标识」「键值对映射」和「值去重」的场景让代码表达更精准、性能更优。学习目标读完本文你将学会Symbol 的用途创建唯一标识、定义常量、模拟私有属性Map 的用法何时用 Map 替代 Object常用 API 与遍历方式Set 的用法去重、集合运算交并差WeakMap 与 WeakSet 的适用场景一、Symbol独一无二的标识符1.1 基本用法Symbol 是一种新的原始数据类型每个 Symbol 值都是唯一的。consts1Symbol();consts2Symbol();console.log(s1s2);// false可以给 Symbol 添加描述仅用于调试constidSymbol(userId);console.log(id.description);// userId1.2 用作对象属性键Symbol 可以作为对象属性键不会与字符串键冲突constuser{name:小明,[Symbol(id)]:123};// Symbol 属性不会出现在 for...in 中for(letkeyinuser){console.log(key);// 只输出 name}// 用 Object.keys 也拿不到console.log(Object.keys(user));// [name]// 需要用专门的 APIconsole.log(Object.getOwnPropertySymbols(user));// [Symbol(id)]1.3 定义常量避免魔法字符串// 传统方式容易冲突、拼写错误constSTATUS_PENDINGpending;constSTATUS_DONEdone;// Symbol 方式绝对唯一constSTATUS{PENDING:Symbol(pending),DONE:Symbol(done),ERROR:Symbol(error)};functionhandle(status){switch(status){caseSTATUS.PENDING:return加载中...;caseSTATUS.DONE:return完成;caseSTATUS.ERROR:return出错了;}}1.4 Symbol.for 与 Symbol.keyForSymbol.for()会在全局注册表中查找或创建 Symbol相同 key 返回同一个 SymbolconstaSymbol.for(app.config);constbSymbol.for(app.config);console.log(ab);// true// 反向查找 keyconsole.log(Symbol.keyFor(a));// app.config// 普通 Symbol 不在全局注册表中constcSymbol(local);console.log(Symbol.keyFor(c));// undefined1.5 常用内置 SymbolWell-Known SymbolsJavaScript 预定义了一些 Symbol用于控制对象行为Symbol作用Symbol.iterator定义对象的默认迭代器for…ofSymbol.toStringTag自定义Object.prototype.toString.call()的返回值Symbol.hasInstance自定义instanceof行为// Symbol.toStringTag 示例classMyClass{get[Symbol.toStringTag](){returnMyClass;}}console.log(Object.prototype.toString.call(newMyClass()));// [object MyClass]二、Map比 Object 更强大的键值对2.1 为什么需要 MapObject 用作键值对有一些局限Object 的局限Map 的优势键只能是字符串或 Symbol键可以是任意类型对象、函数、NaN 等没有直接获取大小的方法有.size属性遍历顺序不保证按插入顺序遍历原型链可能带来意外属性纯净无原型链干扰2.2 基本用法constmapnewMap();// 设置键值对map.set(name,小明);map.set(123,数字键);map.set({id:1},对象键);// 获取值console.log(map.get(name));// 小明console.log(map.get(123));// 数字键// 检查是否存在console.log(map.has(name));// true// 删除map.delete(123);// 大小console.log(map.size);// 2// 清空map.clear();2.3 初始化与迭代// 用数组初始化constuserMapnewMap([[name,小明],[age,18],[city,北京]]);// 遍历键值对for(const[key,value]ofuserMap){console.log(${key}:${value});}// 只遍历键for(constkeyofuserMap.keys()){console.log(key);}// 只遍历值for(constvalueofuserMap.values()){console.log(value);}// forEachuserMap.forEach((value,key){console.log(${key}${value});});2.4 Map 与 Object 互转constmapnewMap([[a,1],[b,2]]);// Map → ObjectconstobjObject.fromEntries(map);console.log(obj);// { a: 1, b: 2 }// Object → Mapconstmap2newMap(Object.entries(obj));console.log(map2);// Map { a 1, b 2 }2.5 实用场景// 用对象做键缓存场景constcachenewMap();functionfetchData(user){if(cache.has(user)){returncache.get(user);// 命中缓存}constdata{/* 请求数据 */};cache.set(user,data);returndata;}constuser1{id:1};fetchData(user1);// 首次请求fetchData(user1);// 命中缓存三、Set自动去重的值集合3.1 基本用法Set 是值的集合每个值只能出现一次。constsetnewSet();set.add(1);set.add(2);set.add(2);// 重复被忽略set.add(3);console.log(set.size);// 3console.log(set.has(2));// trueset.delete(2);console.log([...set]);// [1, 3]3.2 最常用数组去重constnumbers[1,2,2,3,3,3,4];constunique[...newSet(numbers)];console.log(unique);// [1, 2, 3, 4]// 字符串去重constchars[...newSet(hello)];console.log(chars);// [h, e, l, o]3.3 集合运算Set 本身没有内置交并差方法但可以用扩展运算符实现constanewSet([1,2,3]);constbnewSet([2,3,4]);// 并集constunionnewSet([...a,...b]);console.log([...union]);// [1, 2, 3, 4]// 交集constintersectionnewSet([...a].filter(xb.has(x)));console.log([...intersection]);// [2, 3]// 差集a 有但 b 没有constdifferencenewSet([...a].filter(x!b.has(x)));console.log([...difference]);// [1]3.4 遍历 SetconstsetnewSet([red,green,blue]);// for...offor(constcolorofset){console.log(color);}// forEach注意Set 的 forEach 参数是 value, value, setset.forEach((value){console.log(value);});四、WeakMap 与 WeakSet4.1 WeakMapWeakMap 与 Map 类似但有两个关键区别键必须是对象不能是原始值键是弱引用不阻止垃圾回收letuser{name:小明};constweakMapnewWeakMap();weakMap.set(user,额外数据);// 当 user 不再被其他地方引用时usernull;// weakMap 中的条目会被自动回收用途给对象附加私有数据而不影响其生命周期。constprivateDatanewWeakMap();classUser{constructor(name){privateData.set(this,{password:secret});this.namename;}getPassword(){returnprivateData.get(this).password;}}4.2 WeakSetWeakSet 只能存储对象同样是弱引用constvisitednewWeakSet();functionprocess(obj){if(visited.has(obj))return;// 已处理过visited.add(obj);// 处理逻辑...}WeakMap/WeakSet 的限制没有.size属性不能遍历keys/values/entries/forEach 都没有不能清除没有.clear()五、Map vs ObjectSet vs ArrayMap vs Object 怎么选场景推荐键需要是对象/函数Map频繁增删键值对Map需要保持插入顺序Map需要知道大小Map简单的配置对象、JSON 数据Object需要 JSON 序列化ObjectMap 不能直接 JSON.stringifySet vs Array 怎么选场景推荐需要去重Set频繁检查元素是否存在SetO(1)需要有序列表、按索引访问Array需要 map/filter/reduceArray六、常见误区与注意点误区正确理解Symbol 可以用 new 创建new Symbol()会报错用Symbol()Symbol.for(x) Symbol(x)Symbol.for和Symbol创建的是不同的 SymbolSet 去重对对象有效{a:1}和{a:1}是不同的对象都能放入 SetMap 可以用.语法访问Map 用.get()和.set()不是.map.keyWeakMap 可以遍历WeakMap 没有迭代方法不可遍历typeof Symbol()是 “object”typeof Symbol()是 “symbol”Symbol 属性不可枚举的完整含义constobj{name:小明,[Symbol(id)]:123};console.log(JSON.stringify(obj));// {name:小明}console.log(Object.keys(obj));// [name]console.log(Object.getOwnPropertySymbols(obj));// [Symbol(id)]七、动手练习练习 1用 Symbol 实现枚举用 Symbol 定义一组颜色常量避免字符串常量冲突constCOLOR{/* ... */};functiongetColorHex(color){// 返回对应颜色的十六进制值}参考答案constCOLOR{RED:Symbol(red),GREEN:Symbol(green),BLUE:Symbol(blue)};functiongetColorHex(color){constmap{[COLOR.RED]:#ff0000,[COLOR.GREEN]:#00ff00,[COLOR.BLUE]:#0000ff};returnmap[color]||#000000;}console.log(getColorHex(COLOR.RED));// #ff0000练习 2Map 统计字符频率输入一个字符串用 Map 统计每个字符出现的次数functioncountChars(str){// 返回一个 Mapkey 是字符value 是次数}console.log(countChars(hello));// Map { h 1, e 1, l 2, o 1 }参考答案functioncountChars(str){constmapnewMap();for(constcharofstr){map.set(char,(map.get(char)||0)1);}returnmap;}练习 3Set 实现数组交集写一个不依赖扩展运算符的数组交集函数functionintersection(arr1,arr2){// 返回两个数组的交集去重}console.log(intersection([1,2,2,3],[2,3,4]));// [2, 3]参考答案functionintersection(arr1,arr2){constset2newSet(arr2);constresultnewSet();for(constitemofarr1){if(set2.has(item))result.add(item);}return[...result];}八、AI 辅助学习8.1 本节知识点的 AI 提问模板【背景】我是 JavaScript 初学者正在学习第 21 篇Symbol、Map 与 Set。 我已经了解 Object 和 Array 的基本用法。 【问题】我理解 Map 可以替代 Object 做键值对存储但我不太清楚什么时候 应该优先选择 Map。在 React/Vue 这样的框架中哪些场景更适合用 Map 【期望】请对比 Map 和 Object 在以下场景的表现键类型多样性、迭代性能、 内存占用、JSON 序列化。给出 2 个前端开发中适合用 Map 的具体例子。8.2 用 AI 验证你的理解问 AI“typeof Symbol(x)的结果是什么typeof new Map()呢”让 AI 解释“为什么 WeakMap 的键必须是对象”让 AI 出题“写一道 Set 去重和 filter 去重的性能对比题”8.3 警惕 AI 的常见错误AI 可能写出new Symbol()正确是Symbol()AI 可能声称 Map 的键是无序的实际按插入顺序AI 可能在 WeakMap 中使用原始值作为键会报错AI 可能混淆Symbol.for和Symbol的区别九、配套代码本文示例代码位于CODE/21-Symbol-Map-Set/文件名说明data-structures-lab.htmlSymbol、Map、Set 交互式实验室十、本章小结Symbol唯一标识符适合枚举、私有属性用Symbol.for共享Map键可为任意类型按插入顺序有.size支持任意键对象Set值不重复最常用场景是数组/字符串去重WeakMap/WeakSet弱引用键必须是对象不可遍历适合附加元数据选择建议需要任意键用 Map需要去重用 Set需要不影响 GC 用 WeakMap十一、下篇预告下一篇学习面向对象新语法《类Class面向对象的新写法》你将学到class 语法糖与构造函数的对比继承 extends 和方法重写静态属性和私有字段getter/setter 的优雅写法如果本文对你有帮助欢迎点赞、收藏、关注专栏。有任何问题可以在评论区交流