前端问答:Map 和 Object 有啥不同?

哈喽,大家好!👋

作为一名前端开发者,平时在写代码的时候,你是不是经常用到 JavaScript 里的 Object?我们通过它来存储数据,管理键值对,确实很方便。但是,最近我在项目中遇到了一些关于 Object 的安全问题------对象注入攻击(The Dangers of Square Bracket Notation)。这让我开始思考,在实际业务中,有没有更安全、更高效的方式来管理数据呢?

https://github.com/eslint-community/eslint-plugin-security/blob/main/docs/the-dangers-of-square-bracket-notation.md

其实,JavaScript 里还有一个经常被忽视的好帮手,那就是 Map。很多开发者在做业务开发时,可能还不太熟悉什么时候该用 Map,什么时候该用 Object。为了让大家避免掉坑,我今天就结合实际业务场景,来聊聊这两者的区别,以及如何选择最适合的工具。

Object

在 JavaScript 中,Object 作为一种老牌的数据结构,几乎是每个开发者都会接触到的工具。它是由键值对 组成的集合,而键只能是字符串symbol类型。我们经常用 Object 来存储一些基本的用户信息,举个例子:

go 复制代码
const user = {
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com',
};

这个代码看起来很简单直观,想获取用户的名字,直接 user.name 就行了。可是,你可能不知道,Object 的原型继承机制会带来潜在的安全隐患。

Object 的原型链问题

在 JavaScript 中,所有的对象都继承自 Object.prototype,这意味着 Object 本身会带有一些预设的属性和方法。问题来了,如果你不小心允许用户输入 __proto__ 这样的属性,就可能会修改对象的原型链,造成意想不到的后果。

来看个例子:

go 复制代码
user['__proto__'].isAdmin = true;

console.log(user.isAdmin); // true

在这个代码里,我们通过 __proto__ 修改了对象的原型链,给它加上了一个 isAdmin 属性。这个操作可能会让你的应用程序认为某个普通用户是管理员,直接导致安全漏洞

Map

在 JavaScript 的开发过程中,除了我们熟悉的 Object,还有一个更灵活、更安全的工具------Map 。Map 是在 ECMAScript 6 中引入的,相比于传统的 Object,Map 不仅支持任意类型的键值对,还避免了原型链引发的安全问题。下面我们来看一个例子,如何使用 Map 安全地存储用户信息:

go 复制代码
const userMap = new Map([
  ['name', '张三'],
  ['age', 25],
  ['email', 'zhangsan@example.com'],
]);

userMap.set('__proto__', { isAdmin: true });

console.log(userMap.get('__proto__')); // { isAdmin: true }

通过这个例子可以看到,Map 支持任意类型的键 ,比如数字、对象,甚至是 __proto__。与 Object 不同,Map 不会因为 __proto__ 这样的键值影响其内部结构,因为它不会依赖原型链。这意味着,你无需担心用户恶意修改 Map 的原型链,从而引发的安全隐患也就大大减少了。

在这个例子中,我们通过 set 方法给 Map 设置了 __proto__ 键,并为其赋值为 { isAdmin: true }。当我们 get 这个键时,获取到的就是我们存储的内容,而不会影响 Map 本身的结构。

Object VS Map

特性 Object Map
键的类型 仅限于字符串或符号 支持任何数据类型作为键
原型链 继承自原型链,包含属性和方法 没有原型链,提供干净的键值对存储
灵活性 由于键类型限制,灵活性较差 更灵活,支持多种键类型
安全性 易受原型链篡改影响 更安全,无原型链相关问题
遍历顺序 遍历顺序不确定 遵循插入顺序进行遍历

1. 键的类型(Key Types)

  • Object : 键值对中的键必须是字符串或符号(stringsymbol)。这意味着你无法直接用其他类型的数据(如数字、对象等)作为键。

  • Map : 键可以是任何类型的数据,不仅可以是字符串,还可以是对象、数组、甚至其他的 Map。这种灵活性让 Map 在处理复杂数据时有更大的优势。

2. 原型链(Prototype Chain)

  • Object: 对象会继承它的原型链,也就是说它会从其原型对象中继承属性和方法。虽然这种机制强大,但有时会带来一些意想不到的副作用,特别是当你不小心覆盖了对象的原型属性时。

  • Map : Map 没有原型链,存储的键值对只与当前的 Map 实例有关。这意味着 Map 更加干净和安全,避免了原型链带来的潜在风险。

3. 灵活性(Flexibility)

  • Object : 由于键的类型限制,Object 的灵活性稍差。如果你需要更多类型的键(比如对象作为键),那么 Object 就不太合适了。

  • Map : Map 非常灵活,可以存储各种类型的键,尤其是在处理非字符串类型的键时显得非常方便。

4. 安全性(Security)

  • Object: 因为对象继承自原型链,这使得它更容易受到原型链上的属性篡改。例如,原型链上的属性可能会被修改,进而影响你的对象安全。

  • Map : Map 没有原型链的困扰,因此不会出现这些安全隐患,使用起来更加安全。

5. 遍历顺序(Iteration Order)

  • Object: 对象的遍历顺序是不确定的,尤其是当你往对象中添加、删除属性时,顺序可能会变动。

  • Map : Map 的遍历顺序是按照插入的顺序,这让它在需要保证顺序的场景中非常适用。

何时选择Map而非Object?业务需求决定!

在开发中,选择 Map 还是 Object 其实取决于你的具体业务需求。如果你的代码中需要对键的类型有更大的灵活性,那么 Map 是更好的选择。Map 不仅仅支持字符串和符号作为键,还可以使用任何类型的数据,包括对象和数字,而这在 Object 中是做不到的。

1. 灵活的键类型

Map 的一个显著优势在于其键的多样性。在一些复杂的业务场景中,我们经常需要将对象作为键来存储信息,例如用户权限、缓存等场景。这种情况下,Object 的键类型限制就显得力不从心了,而 Map 则提供了完美的解决方案。

2. 安全性更高

另一个关键区别在于安全性。Object 继承自原型链,这意味着它自带很多默认的属性和方法,这些属性可能被攻击者利用进行篡改。而 Map 则没有这种复杂的原型链,因此不存在这些隐患。这使得 Map 在处理用户输入的键值对时更加安全,尤其是对于一些敏感数据的存储,Map 无疑是更优的选择。

3. 保证键值对的顺序

在某些业务场景中,保持键值对的顺序非常重要。例如,在订单处理或审批流程中,操作步骤的顺序直接影响系统的业务逻辑。而在 Object 中,键的顺序是不固定的,可能会随时发生变动,这对依赖顺序的业务场景来说是不可控的。相比之下,Map 可以确保键值对按照插入顺序进行存储和遍历,这在需要有序存储数据的场景中显得尤为重要。

小节

总的来说,如果你的需求涉及到:

  • 需要多样化的数据类型作为键;

  • 需要确保数据存储的顺序不变;

  • 需要更高的安全性防范潜在攻击;那么 Map 无疑是比 Object 更好的选择。

实例讲解:用Map处理复杂业务场景

Map 在JavaScript中的应用非常广泛,特别是在处理复杂数据、动态键值对以及需要保证键值对顺序的场景中,它展现出了极大的灵活性。接下来,我们结合几个典型的业务场景,详细介绍 Map 的应用。

1. 存储复杂数据

在一些业务场景中,你可能需要将一个对象的属性存储为键值对,同时值可能是简单数据或嵌套对象。例如,处理用户信息时,地址通常是一个嵌套的对象结构,使用 Map 可以灵活存储和管理这些数据。

go 复制代码
const personMap = new Map();
personMap.set('name', 'Alice');
personMap.set('age', 30);
personMap.set('address', { city: 'Wonderland', country: 'Fantasia' });

// 取出数据
console.log(personMap.get('name')); // 输出: Alice
console.log(personMap.get('address').city); // 输出: Wonderland

在这个例子中,Map 允许我们存储不同类型的数据,并且可以方便地访问嵌套对象,特别适合处理用户信息等复杂数据结构。

2. 遍历键值对

如果你需要遍历一个键值对集合,Map 提供了非常便捷的方式,保证顺序的同时可以进行高效的遍历操作。例如,在库存管理中,我们可能需要对商品及其数量进行记录,并逐一展示。

go 复制代码
const fruitMap = new Map([
  ['apple', 3],
  ['banana', 5],
  ['orange', 2]
]);

// 使用 forEach 遍历键值对
fruitMap.forEach((value, key) => {
  console.log(`${key} 数量: ${value}`);
});

// 输出:
// apple 数量: 3
// banana 数量: 5
// orange 数量: 2

对于需要严格按照插入顺序展示的数据场景,如商品库存、订单详情等,Map 的遍历功能可以保证数据顺序的一致性。

3. 动态键的处理

在某些业务场景中,我们需要动态地生成键值对,比如处理动态生成的ID或对象。在这种情况下,Map 非常适合使用,因为它允许我们使用对象或其他复杂类型作为键,而 Object 则做不到这一点。

go 复制代码
const dynamicMap = new Map();

const key1 = { name: 'KeyOne' };
const key2 = { name: 'KeyTwo' };

dynamicMap.set(key1, 'Value One');
dynamicMap.set(key2, 'Value Two');

console.log(dynamicMap.get(key1)); // 输出: Value One
console.log(dynamicMap.get(key2)); // 输出: Value Two

这种处理方式非常适合用于动态生成的用户会话、页面元素缓存等场景,能够灵活处理多样化的键。

4. 检查键是否存在

Map 提供了方便的方法来检查某个键是否存在,这在一些业务场景中尤其有用。例如,在汽车信息管理系统中,用户输入的信息是否完整,某个属性是否已经存在,都可以通过 Map 快速检查。

go 复制代码
const carMap = new Map([
  ['make', 'Toyota'],
  ['model', 'Camry'],
  ['year', 2020]
]);

// 检查某个键是否存在
console.log(carMap.has('model')); // 输出: true
console.log(carMap.has('color')); // 输出: false

通过 has() 方法,可以轻松验证某个键是否存在,适用于检查表单数据完整性或配置项是否已定义的场景。

小节

Map 提供了灵活的键值对管理方式,能够处理多种类型的数据,保证顺序,并且在安全性和性能上比 Object 更具优势。无论是存储复杂数据、遍历键值对,还是动态生成和检查键值对,Map 都是非常强大的工具。

希望这些例子能帮助你更好地理解如何在实际业务中应用 Map,如果你在项目中遇到相关问题,不妨尝试使用 Map 来优化你的代码!

结束

在前端开发中,MapObject 各有其适用场景。在处理大量复杂数据、避免原型链问题时,Map 往往是更安全、灵活的选择。而对于一些简单、键类型固定的场景,Object 则更为简洁易用。作为前端开发者,我们需要根据项目需求,合理选择合适的数据结构,这样才能写出高效且优雅的代码。

不知道大家在实际开发中更偏爱哪一个呢?你遇到过什么使用 MapObject 的坑或者心得吗?欢迎在评论区留言,分享你的经验和故事!也许你的一条留言,就能帮到其他小伙伴哦~

关注前端达人,每天一起进步,提升你的前端开发技巧!

相关推荐
Redstone Monstrosity11 分钟前
字节二面
前端·面试
东方翱翔18 分钟前
CSS的三种基本选择器
前端·css
Fan_web41 分钟前
JavaScript高级——闭包应用-自定义js模块
开发语言·前端·javascript·css·html
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
千穹凌帝1 小时前
SpinalHDL之结构(二)
开发语言·前端·fpga开发
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
Hellc0071 小时前
MacOS升级ruby版本
前端·macos·ruby
前端西瓜哥1 小时前
贝塞尔曲线算法:求贝塞尔曲线和直线的交点
前端·算法
又写了一天BUG1 小时前
npm install安装缓慢及npm更换源
前端·npm·node.js
cc蒲公英2 小时前
Vue2+vue-office/excel 实现在线加载Excel文件预览
前端·vue.js·excel