在 JavaScript 中,我们经常需要存储键值对数据。过去,Object 几乎是唯一的选择。但随着语言发展,现在有了专门的 Map 数据结构。虽然 Object 仍然有用,但在很多情况下,Map 确实是更好的选择。
这是 Map 最明显的优势。
Object 的局限:
Object 的键只能是字符串或者 Symbol。如果你使用其他类型(比如对象、数字、函数),它们会被自动转换成字符串。
const obj = {};
const user = { id: 1 };
obj[user] = '张三'; // 键被转换成 "[object Object]"
console.log(obj); // 输出:{ "[object Object]": "张三" }
// 这会导致问题:
const user2 = { id: 2 };
obj[user2] = '李四';
console.log(obj[user]); // 输出:"李四"(覆盖了前面的值)
Map 的解决方案:
Map 允许使用任何类型作为键,包括对象、函数、数字,甚至是 NaN。
const map = new Map();
const user = { id: 1 };
map.set(user, '张三');
console.log(map.get(user)); // 输出:"张三"
const user2 = { id: 2 };
map.set(user2, '李四');
console.log(map.get(user)); // 仍然输出:"张三"
处理特殊值:
const map = new Map();
map.set(NaN, '这不是数字');
console.log(map.get(NaN)); // 输出:"这不是数字"
// 而 Object 会这样:
const obj = {};
obj[NaN] = '这不是数字';
console.log(obj[NaN]); // 输出:"这不是数字",但实际键是字符串 "NaN"
Object 的操作方式:
const obj = {};
// 添加属性
obj.name = '张三';
// 检查属性
if (obj.name) { ... }
// 删除属性
delete obj.name;
// 获取属性数量
const count = Object.keys(obj).length;
Map 的操作方式:
const map = new Map();
// 添加键值对
map.set('name', '张三');
// 检查键
if (map.has('name')) { ... }
// 获取值
const name = map.get('name');
// 删除键值对
map.delete('name');
// 获取大小
const count = map.size;
// 清空所有
map.clear();
Map 的方法更加直观和一致,不需要记住各种特殊情况。
遍历 Object 需要转换:
const user = { name: '张三', age: 25 };
// 遍历键
Object.keys(user).forEach(key => {
console.log(key, user[key]);
});
// 遍历值
Object.values(user).forEach(value => {
console.log(value);
});
// 遍历键值对
Object.entries(user).forEach(([key, value]) => {
console.log(key, value);
});
Map 直接支持迭代:
const userMap = new Map([
['name', '张三'],
['age', 25]
]);
// 直接遍历
userMap.forEach((value, key) => {
console.log(key, value);
});
// 或者使用 for...of
for (const [key, value] of userMap) {
console.log(key, value);
}
Map 本身就是可迭代的,这让代码更加简洁。
Map 会严格按照键值对的插入顺序来维护它们。
const map = new Map();
map.set('first', 1);
map.set('second', 2);
map.set('third', 3);
console.log([...map.keys()]); // 输出:['first', 'second', 'third']
虽然现代 JavaScript 中的 Object 也保持了一定的顺序(字符串键按插入顺序,数字键按数值排序),但 Map 的顺序行为更加明确和可靠。
在以下场景中,Map 通常表现更好:
频繁的增删操作:Map 专门为频繁添加和删除键值对进行了优化。
大型数据集:当存储大量数据时,Map 在内存使用和访问速度方面通常更有优势。
动态键名:当键是动态生成或者经常变化时,Map 的性能更好。
Object 会继承原型链上的属性和方法,这有时会导致意外的问题:
const obj = {};
console.log(obj.constructor); // 输出 Object 构造函数
// 如果某个键名恰好与原型方法同名,就会有问题
obj.hasOwnProperty = '某个值';
// 这会覆盖原生的 hasOwnProperty 方法
Map 完全独立于原型链:
const map = new Map();
map.set('hasOwnProperty', '安全的值');
map.set('constructor', '另一个值');
console.log(map.get('hasOwnProperty')); // 输出:"安全的值"
// 不会影响任何原型方法
例子1:词频统计
// 使用 Map
const text = "苹果 香蕉 苹果 橙子";
const wordCount = new Map();
text.split(' ').forEach(word => {
const count = wordCount.get(word) || 0;
wordCount.set(word, count + 1);
});
console.log(wordCount.get('苹果')); // 输出:2
例子2:对象作为键(Map 的优势场景)
// 用户权限管理系统
const user1 = { id: 1, name: '张三' };
const user2 = { id: 2, name: '李四' };
const permissions = new Map();
permissions.set(user1, ['读取', '写入']);
permissions.set(user2, ['读取']);
// 直接使用用户对象来获取权限
console.log(permissions.get(user1)); // 输出:['读取', '写入']
// 如果用 Object 就会有问题:
const badPermissions = {};
badPermissions[user1] = ['读取', '写入'];
badPermissions[user2] = ['读取'];
console.log(badPermissions[user1]); // 输出:['读取'](被覆盖了)
虽然 Map 有很多优点,但 Object 在以下情况仍然合适:
简单的静态数据:当键是固定的字面量时,Object 字面量语法更简洁:
// 这样写很简洁
const config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retry: 3
};
JSON 序列化:Object 可以直接用 JSON.stringify() 和 JSON.parse() 处理:
const obj = { name: '张三', age: 25 };
const json = JSON.stringify(obj); // 直接支持
const map = new Map([['name', '张三'], ['age', 25]]);
const mapJson = JSON.stringify(map); // 输出:"{}"(不是我们想要的)
方法定义:当需要定义对象方法和使用 this 上下文时。
在选择数据结构时,可以遵循这样的原则:
使用 Map 的情况:
键的类型复杂(对象、函数等)
需要频繁添加或删除键值对
需要保持插入顺序
需要快速获取键值对数量
担心原型链污染
使用 Object 的情况:
键是简单的字符串或 Symbol
需要进行 JSON 序列化
需要定义对象方法
数据结构和逻辑相对简单
随着 JavaScript 的发展,Map 已经成为处理键值对数据的现代选择。下次你需要存储键值对时,不妨考虑一下 Map,它可能会让你的代码更加清晰和高效。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
Javascript 一直是神奇的语言。 不相信我? 尝试使用map和parseInt将字符串数组转换为整数。打开 Chrome 的控制台(F12),粘贴以下内容,然后按回车,查看输出结果:
在 Javascript 中,一个函数可以传递任何多个数量的参数,即使调用时传递的数量与定义时的数量不一致。缺失的参数会以 undefined 作为实际值传递给函数体,然后多余的参数会直接被忽略掉
在JavaScript中,Map 是存储键/值对的对象。Map 类似于一般 JavaScript 对象 ,但对象与 Map 之间一些关键的差异使 Map 很有用。如果你要创建一个存储一些键/值路径的 JavaScript 对象
Map、Set的polyfill实现是可以继承的;//可继承的Array替换原生Array,Array要改的地比较多,除了替换原生Array还需修改继承函数,供参考
普通的 JavaScript 对象通常可以很好地保存结构化数据。但是它们有一些限制:只能用字符串或符号用作键,自己的对象属性可能会与从原型继承的属性键冲突(例如,toString,constructor 等)。对象不能用作键
Map的出现解决了传统object无法直接解决的问题,更好地向标准编程语言靠近(标准编程语言一般会提供Map集合),使用的坑也比较少(比如没有object作为key时转换为[object Object]的问题)。
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
ES6的Map的键可以是任意的数据结构,并且不重复。那么map的底层原理是啥呢?Map利用链表,hash的思想来实现。首先,Map可以实现删除,而且删除的数据可以是中间的值。
JavaScript中,数组的遍历我们肯定都不陌生,最常见的两个便是forEach 和 map。(当然还有别的譬如for, for in, for of, reduce, filter, every, some, ...)
JS 普通对象 {key: value} 用于存放结构化数据。但有一件事我觉得很烦:对象键必须是字符串(或很少使用的 symbol)。如果将数字用作键会怎样? 在这种情况下不会有错误:
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!