javascript - 字典:JavaScript 中的映射与对象

标签 javascript dictionary object data-structures

在 JavaScript 中,ObjectMap 都可以用于“字典”(键值对存储),因为它们允许您执行所有基本的读写操作当使用这样的数据结构时,您会期望:“设置”、“获取”、“删除”和“检测”键、值和条目。从历史上看,在 JavaScript 中存在 Map 之前,我们就一直使用 Object 来存储字典。这完全没问题,但是,在某些情况下,有一些重要的差异使得 Map 更可取,可以在此处找到: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#objects_vs._maps

就我个人而言,就我目前正在处理的情况而言,我希望使用 Map 而不是 Object,因为我觉得 Map 会更适合我目前想要实现的目标:包含数亿条目的庞大字典、大量读/写操作,并且基于自定义基准,Map 似乎比 Object 快得多(同样,就我而言)。

不幸的是,当迭代我的 Map 字典时(Map 是可迭代的,因此可以直接使用 for...of 进行迭代),我面临着“迭代和变异”问题:在Map上进行迭代时,您不应该在迭代中改变相同的Map。这是一种反模式!

对于Object(它不实现迭代协议(protocol),因此默认情况下不能使用for...of直接迭代对象),我使用Object.keys()Object.values()Object.entries() 返回给定字典自身可枚举属性名称的数组(keys )、值或字符串键控属性 [key, value] 对。因此,因为我不是在字典本身上进行迭代,而是实际上在内存中的一个单独的数组上进行迭代,所以我可以安全地写入/改变我的字典,而不会遇到与“迭代和改变”相关的任何问题。 “问题。

当使用 Map 作为字典时,为了迭代和变异,一种解决方案是克隆(浅复制或深复制)字典,然后迭代原始字典,但仅变异克隆。当迭代周期结束时,将克隆分配给原始字典并删除现在无用的克隆。这需要在内存中克隆原始 Map 字典,这会占用不必要的空间。

是否有比克隆更好的方法来规避与 Map 字典相关的“Iterate-and-Mutate”反模式/问题?

谢谢!

示例:

  1. 对象字典 -> 有效!
// Object dictionary -> WORKS!
const numbers = {
  '0': 0,
  '1': 1,
  '2': 2,
  '3': 3,
  '4': 4,
  '5': 5,
};

function compute(numbers) {
  /*
   * The Object.values() method returns an array of a given object's own
   * enumerable property values, in the same order as that provided by a
   * for...in loop.
   */
  for (number of Object.values(numbers)) {
    console.log('number:', number);

    const square = number * number;

    if (!numbers[`${square}`]) {
      numbers[`${square}`] = square;
    }
  }

  return numbers;
}

console.time('timer');
const result = compute(numbers);
console.timeEnd('timer');

console.log('result:', result);
  • map 字典 -> 不能按原样工作!
  • // Map dictionary -> DOESN'T WORK AS IS!
    const numbers = new Map([
      ['0', 0],
      ['1', 1],
      ['2', 2],
      ['3', 3],
      ['4', 4],
      ['5', 5],
    ]);
    
    function compute(numbers) {
      /*
       * The values() method returns a new iterator object that contains the values
       * for each element in the Map object in insertion order.
       */
      for (number of numbers.values()) {
        console.log('number:', number);
    
        const squared = number * number;
    
        if (!numbers.has(`${squared}`)) {
          // "Iterate-and-Mutate" anti-pattern/problem:
          numbers.set(`${squared}`, squared);
        }
      }
    
      return numbers;
    }
    
    console.time('timer');
    const result = compute(numbers);
    console.timeEnd('timer');
    
    console.log('result:', result);
    
  • 映射字典 + 克隆(深度复制)-> 有效!(速度慢且占用大量内存)
  • // Map dictionary + clone (Deep Copy) -> WORKS!
    let numbers = new Map([
      ['0', 0],
      ['1', 1],
      ['2', 2],
      ['3', 3],
      ['4', 4],
      ['5', 5],
    ]);
    
    function compute(numbers) {
      /*
       * We create a clone (Deep Copy).
       * The data itself is cloned (slow and take a lot of memory).
       */
      const clone = new Map([ ...numbers.entries() ]);
    
      // Then, we iterate on the original dictionary.
      for (number of numbers.values()) {
        console.log('number:', number);
    
        const square = number * number;
    
        if (!clone.has(`${square}`)) {
          // But we mutate the clone, not the original dictionary.
          clone.set(`${square}`, square);
        }
      }
    
      // Finally, we assign 'clone' to original dictionary 'numbers'.
      numbers = clone;
      // And delete 'clone'.
      delete clone;
    
      return numbers;
    }
    
    console.time('timer');
    const result = compute(numbers);
    console.timeEnd('timer');
    
    console.log('result:', result);
    
  • 映射字典 + 克隆(浅复制)-> 有效!(比深复制更快,占用的内存更少,但仍然......)
  • // Map dictionary + clone (Shallow Copy) -> WORKS!
    let numbers = new Map([
      ['0', 0],
      ['1', 1],
      ['2', 2],
      ['3', 3],
      ['4', 4],
      ['5', 5],
    ]);
    
    function compute(numbers) {
      /*
       * We create a clone (Shallow Copy).
       * The data itself is not cloned (faster and take less memory than Deep Copy).
       */
      const clone = new Map(numbers);
    
      // Then, we iterate on the original dictionary.
      for (number of numbers.values()) {
        console.log('number:', number);
    
        const square = number * number;
    
        if (!clone.has(`${square}`)) {
          // But we mutate the clone, not the original dictionary.
          clone.set(`${square}`, square);
        }
      }
    
      // Finally, we assign 'clone' to original dictionary 'numbers'.
      numbers = clone;
      // And delete 'clone'.
      delete clone;
    
      return numbers;
    }
    
    console.time('timer');
    const result = compute(numbers);
    console.timeEnd('timer');
    
    console.log('result:', result);
    
  • map 字典 + 假克隆 -> 不起作用,因为不够!
  • // Map dictionary + fake clone -> DOESN'T WORK BECAUSE INSUFFICIENT!
    let numbers = new Map([
      ['0', 0],
      ['1', 1],
      ['2', 2],
      ['3', 3],
      ['4', 4],
      ['5', 5],
    ]);
    
    function compute(numbers) {
      /*
       * We create a "clone" (we believe we do, but in reality we don't!).
       * The 'clone' variable is not really a clone/copy of 'numbers'.
       * It's just a variable that points to the same data in memory.
       * Therefore, any mutation on 'numbers' will be reflected on 'clone',
       * which breaks the solution!
       */
      const clone = numbers;
    
      // Then, we iterate on the original dictionary.
      for (number of numbers.values()) {
        console.log('number:', number);
    
        const square = number * number;
    
        if (!clone.has(`${square}`)) {
          // But we mutate the clone, not the original dictionary.
          clone.set(`${square}`, square);
        }
      }
    
      // Finally, we assign 'clone' to original dictionary 'numbers'.
      numbers = clone;
      // And delete 'clone'.
      delete clone;
    
      return numbers;
    }
    
    console.time('timer');
    const result = compute(numbers);
    console.timeEnd('timer');
    
    console.log('result:', result);
    

    最佳答案

    如果您想模仿普通对象使用的模式(即迭代 Object.values),则迭代 [...numbers.values()],这实际上是 Map 的等价物。空间成本相似——在这两种情况下都会创建一个包含值的数组。

    所以:

    const numbers = new Map([['0', 0],['1', 1],['2', 2],['3', 3],['4', 4],['5', 5]]);
    
    function compute(numbers) {
      for (const number of [...numbers.values()]) {
        console.log('number:', number);
        const squared = number * number;
        if (!numbers.has(`${squared}`)) {
          numbers.set(`${squared}`, squared);
        }
      }
      return numbers;
    }
    
    console.log('result:', ...compute(numbers));

    关于javascript - 字典:JavaScript 中的映射与对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71820682/

    相关文章:

    javascript - 如何等待在 Protractor 端到端测试中处理事件?

    C# 两个数字列表之间的逐元素差异

    python - 更改 __hash__ 的类仍然适用于字典访问

    php - 将数组对象转换为字符串并分隔值

    java - 我类(class)的数据为空?

    javascript - 当最后一只猫显示完整图像时如何发出警报?

    javascript - 更改网络聊天控件中建议操作按钮的字体

    使用继承的c++语法

    javascript - 返回上一页

    ios - gpx 文件中的地理定位和跟踪