Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

何时使用 Map 来代替普通的 JS 对象 #195

Open
husky-dot opened this issue Feb 25, 2020 · 0 comments
Open

何时使用 Map 来代替普通的 JS 对象 #195

husky-dot opened this issue Feb 25, 2020 · 0 comments

Comments

@husky-dot
Copy link
Owner

作者:Dmitri Pavlutin
译者:前端小智
来源:dmitripavlutin

点赞再看,养成习惯

本文 GitHub https://github.com/qq449245884/xiaozhi 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

JS 普通对象 {key: 'value'} 用于存放结构化数据。但有一件事我觉得很烦:对象键必须是字符串(或很少使用的 symbol)。

如果将数字用作键会怎样? 在这种情况下不会有错误:

const names = {
  1: 'One',
  2: 'Two',
};

Object.keys(names); // => ['1', '2']

JS 会隐式地将对象的键转换为字符串,这种默认行为丢失了类型的一致性,要解决也挺棘手的。但 ES6 中的Map 对象可以帮我们解决这类的问题,Look See See。

1. Map 接受任何类型的键

如前所述,如果对象的键不是 stringsymbol,JS 将隐式地将其转换为字符串。

幸运的是,map 的键类型没有问题

const numbersMap = new Map();

numbersMap.set(1, 'one');
numbersMap.set(2, 'two');

[...numbersMap.keys()]; // => [1, 2]

12numbersMap 中的键,这些键的类型(数字)保持不变。

可以在 mpa 中使用任何键类型:数字、布尔值、字符串和 symbol

const booleansMap = new Map();

booleansMap.set(true, "Yep");
booleansMap.set(false, "Nope");

[...booleansMap.keys()]; // => [true, false]

booleansMap 使用 booleans 作为键,没有问题。相反,布尔键在普通对象中不起作用。

来突破一下想象:是否将整个对象作为 map 的键,答案:可以的。

1.1 对象作为键

假设你需要存储一些与对象相关的数据,而不需要将这些数据附加到对象本身。使用普通对象是不可能的。

解决方法是使用对象-值元组数组:

const foo = { name: 'foo' };
const bar = { name: 'bar' };

const kindOfMap = [
  [foo, 'Foo related data'],
  [bar, 'Bar related data']
]

kindOfMap 是一个数组,包含对象和关联值的对。

这种方法最大的问题是按键访问值的复杂度O(n),咱们必须遍历整个数组才能通过键获得所需的值。

function getByKey(kindOfMap, key) {
  for (const [k, v] of kindOfMap) {
    if (key === k) {
      return v;
    }
  }
  return undefined;
}

getByKey(kindOfMap, foo); // => 'Foo related data'

WeakMap (Map的一个专门版本)不需要这么麻烦就能做到上面的事情:它只接受对象作为键

MapWeakmap 之间的主要区别是,Weakmap 允许对键对象进行垃圾收集,从而防止内存泄漏。

好了,用 WeakMap 重构上面的代码就变得很简单了:

const foo = { name: 'foo' };
const bar = { name: 'bar' };

const mapOfObjects = new WeakMap();

mapOfObjects.set(foo, 'Foo related data');
mapOfObjects.set(bar, 'Bar related data');

mapOfObjects.get(foo); // => 'Foo related data'

Map 相反,WeakMap 只接受对象作为键,并少了一些方法

2. map 对键名没有限制

JS 中的任何对象都从原型对象继承属性,普通对象也是如此。

如果重写从原型继承的属性,则可能会破坏依赖这些原型属性的代码:

function isPlainObject(value) {
  return value.toString() === '[object Object]';
}

const actor = {
  name: 'Harrison Ford',
  toString: 'Actor: Harrison Ford'
};

// Does not work!
isPlainObject(actor); // TypeError: value.toString is not a function

在对象参与者上定义的属性 toString 覆盖从原型继承的 toString() 方法。这中断了isObject(),因为它依赖于 toString() 方法。

检查普通对象从原型继承的属性和方法的列表, 避免使用这些方法名定义自定义属性。

例如,假设有一个管理某些自定义字段的用户界面。 用户可以通过指定名称和值来添加自定义字段:

将定制字段的状态存储到普通对象中会很方便:

const userCustomFields = {
  'color':    'blue',
  'size':     'medium',
  'toString': 'A blue box'
};

但是用户可能会选择一个自定义字段名称,例如toString(如示例中所示),构造函数等,这可能会破坏咱们的对象。

不要使用用户输入的值作为普通对象上键。

map 没有这个问题,键值名称不受限制:

function isMap(value) {
  return value.toString() === '[object Map]';
}

const actorMap = new Map();

actorMap.set('name', 'Harrison Ford');
actorMap.set('toString', 'Actor: Harrison Ford');

// Works!
isMap(actorMap); // => true

不管 actorMap 有一个名为toString的属性,toString()方法都可以正常工作。

3. map 是可迭代

为了遍历普通对象的属性,必须使用其他的辅助静态函数,如 Object.keys()Object.entries():

const colorsHex = {
  'white': '#FFFFFF',
  'black': '#000000'
};

for (const [color, hex] of Object.entries(colorsHex)) {
  console.log(color, hex);
}
// 'white' '#FFFFFF'
// 'black' '#000000'

Object.entries(colorsHex) 返回从对象提取的键值对数组。

但是,map 本身是可迭代的:

const colorsHexMap = new Map();

colorsHexMap.set('white', '#FFFFFF');
colorsHexMap.set('black', '#000000');

for (const [color, hex] of colorsHexMap) {
  console.log(color, hex);
}
// 'white' '#FFFFFF'
// 'black' '#000000'

colorsHexMap是可迭代。可以在任何接受迭代的地方使用它:for()循环,展开运算符[...map]

map 提供了返回可迭代方法:map.keys() 遍历键,map.values() 遍历值

4. map 的大小

普通对象的另一个问题是,您无法立马知道它包含的属性的数量。

const exams = {
  'John Smith': '10 points',
  'Jane Doe': '8 points',
};

Object.keys(exams).length; // => 2

要确定 exams 的大小,必须通过所有键来确定它们的数量。

map 提供了 size 属性,表示属性的数量。

const examsMap = new Map([
  ['John Smith', '10 points'],
  ['Jane Doe', '8 points'],
]);
  
examsMap.size; // => 2

确定 map 的属性的数量更加简单:examsMap.size

总结

普通 JS 对象通常可以很好地保存结构化数据,但它们也有一些局限性:

  1. 只能用字符串或 sybmol 作为键
  2. 自己的对象属性可能会与从原型继承的属性键冲突(例如 toStringconstructor等)。
  3. 对象不能用作键

所有这些问题都可以通过 map 轻松解决。 而且,它们提供了诸如迭代器和易于进行大小查找之类的好处。

不要将 map 当作普通对象的替代品,而应视为是普通对象补充。

你还知道 map 比普通物体的其他好处吗?请在下方留言。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文: https://dmitripavlutin.com/maps-vs-plain-objects-javascript/

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant