ES6 映射是如何在底层实现的?

Posted

技术标签:

【中文标题】ES6 映射是如何在底层实现的?【英文标题】:How are ES6 maps implemented under the hood? 【发布时间】:2020-02-20 22:06:30 【问题描述】:

在一次采访中,我被要求实现键/值对的数据结构,其中键可以是一个对象,我知道这可以使用 ES6 映射,但是它们如何在 javascript 中的引擎盖下工作,其中键被严格字符串化并且还实现了与哈希表/对象相同的恒定查找时间?

谢谢。

【问题讨论】:

我猜 Maps 使用对象的引用作为哈希键。 是的,这是有道理的,但是如果更改该引用,那么已经生成的哈希索引将无效? 如何更改对象的引用?如果一个对象有不同的引用,它就是一个不同的对象。 是的,但是Mapget/set 中使用的键与引用对象属性的键不同。而且这些映射键没有被字符串化。 @t.niese 确实如此。这有点术语冲突——在 JS 中,通常“key”指的是对象的属性。一般来说,在谈论地图时,“键”只是用于查找关联值的项目。这变得很混乱,因为普通的 JS 对象 do 以相同的方式使用键,因此键在 JS 中成为“属性名称”的同义词,这是 正确的,因为它们以相同的方式工作用于在良好但不正确的情况下获取值,因为地图通常不会通过其属性在地图中获取值。因此,存在抽象泄漏。 【参考方案1】:

我在一次采访中被问到这个问题,我无法保证 Map 是如何在后台实际实现的(最终在这里寻找自己)。然而,这是我与面试官一起制定的方法(注意,要求仅针对 DOM 节点键,但我确实认为对象是最难处理的键,其余的可以通过额外的代码轻松处理),我认为它至少是有见地的:

class Map 
  constructor() 
    this.map = ; // internal key/value object
    this.trackerKey = Symbol();
  

  set(key, value) 
    let lookupKey = key[this.trackerKey];
    if (!lookupKey) 
      lookupKey = Symbol();
      key[this.trackerKey] = lookupKey;
    
    this.map[lookupKey] = value;
  

  has(key) 
    return key.hasOwnProperty(this.trackerKey);
  

  get(key) 
    const lookupKey = key[this.trackerKey];
    return this.map[lookupKey];
  

  delete(key) 
    const lookupKey = key[this.trackerKey];
    delete key[this.trackerKey];
    delete this.map[lookupKey];
  

基本上,这个想法是在底层使用符号来跟踪对象。 trackerKey 是一个(符号)属性,我们添加到所有传入的键中。由于它是在实例内部定义的符号,因此其他任何东西都无法引用它。

所以当我们转到set 时,我们检查对象上是否存在trackerKey 属性。如果不是,我们将其设置为新符号。然后,我们将内部映射中该键的值设置为传入的值。

hasget 现在是相当简单的查找。我们检查我们的跟踪器密钥是否存在于密钥上,以查看它是否包含在我们的 Map 中。对于get,我们可以简单地从它的 trackerKey 属性中获取对象的内部查找键。

对于delete,我们只需要从key对象中删除trackerKey属性,然后在我们内部的map对象中删除该属性即可。 有趣而有见地的练习!希望这会有所帮助:)

【讨论】:

"没有其他东西可以引用它" -- trackerKey 可以通过Object.getOwnPropertySymbolsReflect.ownKeys 从外部检索,但你是对的因为没有代码通常会这样做。 @FZs 很好的标注。这是一个很好的观点,可以使用这些方法检索符号键。但我们至少保证外部代码访问trackerKey 的唯一方法是通过您提到的反射,否则我们保证trackerKey 中的唯一性,这样外部代码就不会意外 i> 覆盖或与之碰撞。 注意,如果对象已被密封或冻结,这也不起作用。不确定我们如何处理该用例【参考方案2】:

Map 的键不是Map 对象的键。它们是 .get.set 方法的参数。

 const map = new Map;
 const key = ;
 map.set(key, "stuff"); // key is passed as an argument, not stringified
 map[key] // key would get stringified here according to language semantics, but even then it would be undefined as maps keys aren't keys of the map object

这些方法在后台所做的是具体实现。

【讨论】:

确实是的,我意识到,我的问题有点含糊,是否有任何资源可以挖掘以了解 .get/.set 的工作原理? 更准确的说法是 Map 的键不是 Map 对象的 properties。它们仍然可以称为“钥匙”。 您可以深入研究规范以找到这些方法必须满足的规则,或其中一种实现的源代码

以上是关于ES6 映射是如何在底层实现的?的主要内容,如果未能解决你的问题,请参考以下文章

探索 Class 底层原理

NSDictionary底层实现原理

orm之sqlarchemy 底层实现原理

手写SpringMVC,实现SpringMVC容器,加底层@RequestMapping的映射

GoLanggolang底层数据类型实现原理

stl容器区别: vector list deque set map及底层实现