【问题标题】:JavaScript: reverse mapping from an object id to that objectJavaScript:从对象 id 到该对象的反向映射
【发布时间】:2021-03-05 06:37:32
【问题描述】:

我有一个 Rectangle 类,它有一个名为 id 的 prop,它指示 Rectangle 实例的唯一性

class Rectangle {
  constructor(desc) {
    this.desc = desc
    this.id = Symbol()
  }
}

稍后我需要将id 放入其他数据结构中,并且我需要一种使用id 的快速方法来查找Rectangle 实例

为此,我需要手动创建地图,例如

const r1 = new Rectangle('first')
const r2 = new Rectangle('second')
const r3 = new Rectangle('third')

const map = {
  [r1.id]: r1,
  [r2.id]: r2,
  [r3.id]: r3,
}

所以如果需要,我可以获取r1 的参考

map[r1.id]

我想知道是否有一种编程方式来实现这种反向映射,其中id 是键,Rectangle 实例是值?

另外,如果有人可以为地图建议一个更好的名称,我将不胜感激 - 现在名称 map 并没有真正的描述性。

最后,我想知道我们是否可以摆脱id 并只使用Rectangle 实例的引用?我知道答案可能是“视情况而定”,但我认为大多数时候我们需要id 来处理实例或对象。我想知道在什么情况下只使用引用不会削减它?

【问题讨论】:

  • 你想达到什么目的?而不是地图,它可能是“数字”。您需要有关订单的信息吗?数字可以存储集合/数组。我会存储引用 - 所以对矩形的访问将在语言级别上解决......
  • @Ouroborus 是的,我知道 JavaScript 中的每个对象都是不同的内存指针,它们永远不会相等。需要id 不是因为我不知道我只能比较参考,而是因为它是更大系统的一部分。
  • @Ouroborus 键也可以是 javascript 中的符号。我不确定我是否理解你在这里的建议。你能写一个答案吗?谢谢
  • 嗨,这看起来像多个问题。
  • “也”位非常小,我可能不会为此说什么,但“最后”位是一个完全不同的问题,不适合您的标题。我认为这是一个很好的问题,应该引起它自己的关注,如果它还没有出现在网站上的话。

标签: javascript oop


【解决方案1】:

您可以将矩形收集到一个数组中并使用 reduce:

const r1 = new Rectangle('first')
const r2 = new Rectangle('second')
const r3 = new Rectangle('third')

const input = [r1, r2, r3];
const map = input.reduce((output, rect) => ({...output, [rect.id]: rect}), {})
console.log(map)

【讨论】:

    【解决方案2】:

    如果您愿意将外部库集成到项目中,一种可能性是使用 Lodash 的 keyBy 方法。它将一个对象数组作为第一个输入,并使用一个函数来计算结果对象的键。

    如果我以你的为例,这将是结果代码:

    import { keyBy } from 'lodash';
    
    const r1 = new Rectangle('first');
    const r2 = new Rectangle('second');
    const r3 = new Rectangle('third');
    
    const map = keyBy([r1, r2, r3], (rectangle) => rectangle.id);
    
    

    您仍然需要自己构建数组,但无论如何都必须这样做。如果你想要一个更复杂的数据结构,你可以创建一个具有数据的内部数组表示的类,以及一些访问它的方法。像这样的东西(我正在使用打字稿,javascript 会类似但没有打字,另外我没有测试这是否适用于复制和粘贴,可能需要一些小的调整):

    import { keyBy, filter } from 'lodash';
    
    class RectangleMap {
      private recArray: Rectangle[];
    
      constructor() {
        this.recArray = [];
      }
    
      addRectangle(r: Rectangle): void {
        this.recArray.push(r);
      }
    
      removeRectangle(r: Rectangle): void {
        this.recArray = filter(this.recArray, (rec) => rec.id !== r.id);
      }
    
      getMap(key: string = 'id'): Record<string, Rectangle> {
        return keyBy(this.recArray, rec => rec[key]);
      }
    }
    
    const myRecMap = new RectangleMap();
    
    myRecMap.addRectangle(new Rectangle('first'));
    myRecMap.addRectangle(new Rectangle('second'));
    myRecMap.addRectangle(new Rectangle('third'));
    
    console.log(myRecMap.getMap());
    
    

    一般来说,我总是推荐 Lodash 用于此类数组/对象操作。它们具有非常好的实用功能。

    【讨论】:

    • 嘿,谢谢你的回答。但在这种情况下,id 似乎不是Rectagnle 实例内部的符号,对吧?为什么我们要把它们变成字符串?
    • 在这种情况下,地图中每个矩形的键将是其内部 id。为了更清楚,getMap() 可以使用 rec.id 而不是 rec[key],但是无法自定义 key。我假设 id 将采用字符串的形式,但它们也可以是其他形式。在这种情况下,需要调整 Record 类型(打字稿对可以用作键索引的内容有一些限制,所以我建议坚持使用符号、字符串或数字等基本类型)。
    【解决方案3】:

    我想知道是否有一种编程方式来实现这种反向 映射其中 id 是键,Rectangle 实例是值?

    如果您需要的数据结构是一个以id 为键的映射,那么在没有显式键值插入的情况下,没有已经构建的方法可以做到这一点。您可能有 2 个选择:

    • 比创建地图的方式稍有改进:

      class Rectangle {
        constructor(desc) {...}
      
        getAsMapEntry() {
          return [this.id, this]
        }
      }
      
      const map = new Map([
        r1.getAsMapEntry(),
        r2.getAsMapEntry(),
        r3.getAsMapEntry()
      ]);
      

      根据您的情况,您应该决定是将getAsMapEntry 方法保留在Rectangle 类中还是作为实用方法。为简单起见,我展示了第一种方法,但我可能会选择第二种方法。此外,我强烈建议您在这里使用Map 类,对于许多reasons

    • 您可以创建一个新的数据结构,它接受Rectangles(或者更好的是,所有具有id 属性的对象)并在内部有一个映射。这个新类就像一个适配器,至少使您需要的操作更具可读性。不要扩展Map 类,因为这种继承会破坏Liskov Principle,因为您必须彻底改变set 方法的先决条件。更简单的方法是按组合进行:

      class EntityContainer {
          // accept an array of Rectangle (or objects having the id property)
          constructor(entities) {
              this._internalMap = new Map(entities.map(e => [e.id, e]));
          }
      
          _assertIsEntity(entity) {
              if (!entity.id)
                  throw "not an entity"
          }
      
          insertOrUpdate(entity) {
              this._assertIsEntity(entity);
              this._internalMap.set(entity.id, entity);
          }
      
          deleteByEntity(entity) {
              this._assertIsEntity(entity);
              this.deleteById(entity.id);
          }
      
          deleteById(id) {
              this._internalMap.delete(id)
          }
      
          getById(id) {
              this._internalMap.get(id);
          }
      }
      

    这种方法的好处是您可以在EntityContainer 类中隐藏基于id 的逻辑,这意味着您插入实体,而不是例如id-entity 对。如果你当然需要,deleteByEntity 的删除也是如此。您也可以验证实体,并且您通常可以更好地控制您所做的操作,但代价是要维护一个新类。当您编写的方法简单地投影_internalMap 方法时,就会出现缺点,请参阅getById。这不是很漂亮,但我认为如果您只需要几种方法就可以接受。

    另外,如果有人能建议一个更好的名字,我将不胜感激 地图 - 现在名称地图并没有真正的描述性。

    当然,这是你必须决定的事情,这取决于它的目的。

    最后,我想知道我们是否可以摆脱 id 而只使用 矩形实例?我知道答案可能是“视情况而定”,但是 我认为大多数时候我们需要实例的 id 或 对象。我想知道在什么情况下只使用引用是 不剪吗?

    再一次,您没有向我们提供足够的信息,无法就使用参考还是使用id 属性是否更合适提供建议(只是为了清楚起见,一如既往,没有规则)。根据您的评论“它是更大系统的一部分”,id 可能是一个很好的权衡,因为它是在多个组件之间创建关系的更简单方法,尤其是当外部数据存储是例如涉及。

    【讨论】:

    • 嘿,你能举一个你提到的第二种方法的例子吗?
    【解决方案4】:

    我认为最好在 Rectangle 类中创建 2 个静态属性并使用它们来跟踪实例

    
    class Rectangle {
      constructor(desc) {
        this.desc = desc;
        this.id = Symbol();
        Rectangle.instances[this.id] = this;
      }
    
      static instances = {};
      static getInstance = (id) => {
        return this.instances[id];
      }
    }
    
    let r1 = new Rectangle('one');
    let r2 = new Rectangle('two');
    
    console.log(Rectangle.getInstance(r1.id)); // gives the r1 instance
    

    【讨论】:

      【解决方案5】:

      id 是键,而 Rectangle 实例是值?

      它的措辞很奇怪(如果我理解你的意思,我不确定确定,但似乎使用static 最适合将创建的Rectangles 存储在类似于@ 的数据结构中987654324@)

      PS:如果你打开inspect元素,你会看到这些对象的ids(虽然显示器没有显示)

      class Rectangle {
        constructor(desc) {
          this.desc = desc
          this.id = Symbol(desc)
          Rectangle.list[this.id]=this
        }
        static list={} //this is an your map(kinda)
        static getRectangleById=(id)=>Rectangle.list[id]||null
        //getRectangleById would return null if id is invalid
      }
      
      let r1=new Rectangle("first")
      let r2=new Rectangle("second")
      let r3=new Rectangle("third")
      
      //example usage
      //btw these objects do have 2 keys, just that THIS DOESNT LOG SYMBOLS
      let locatedRect=Rectangle.getRectangleById(r1.id) //null if invalid
      console.log(locatedRect)
      
      let sameRect=Rectangle.list[r1.id] //undefined if invalid
      console.log(sameRect)
      
      console.log(locatedRect==sameRect)
      console.log(Object.keys(sameRect)) //proof that stack snippets don't show symbols >:{

      【讨论】:

        【解决方案6】:
        const r1 = new Rectangle('first')
        const r2 = new Rectangle('second')
        const r3 = new Rectangle('third')
        
        const rectangles = [r1, r2, r3];
        const map = rectangles.reduce((output, rectangle) => ({...output, [rectangle.id]: rectangle}), {})
        console.log(map)
        

        通过一一缩小地图中的矩形,我们基本上可以使用[rectangle.id]: rectangle 以id 作为键来插入矩形。

        Running example

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-01-23
          • 2012-02-06
          • 1970-01-01
          • 1970-01-01
          • 2020-06-23
          • 2014-11-01
          • 2019-09-19
          • 1970-01-01
          相关资源
          最近更新 更多