【问题标题】:ES6 Map: Why/When to use an object or a function as a key?ES6 Map:为什么/何时使用对象或函数作为键?
【发布时间】:2017-12-15 21:20:50
【问题描述】:

我已阅读 Map 是什么,并了解 Object 与 Map 之间的区别。我不明白为什么我会使用objectsfunctions 作为Map 允许的键。

问题:为什么以及何时将objectfunction 设置为密钥?

【问题讨论】:

  • 只有在使用特定类型的Objects 的身份时才需要这样做。但我现在想不出一个用例。无论如何,Map 等。如果 Javascript 有 value objects 会更有用。
  • 这个问题比“Maps vs Objects in ES6, When to use?”具体得多!

标签: javascript dictionary ecmascript-6


【解决方案1】:

基本上,如果您想跟踪与某个对象相关但由于某种原因不应出现在对象本身上的任何信息,您可以使用地图。

一个简单的例子是跟踪与对象相关的操作完成了多少次。

这是一个演示,我们在不影响动物本身的情况下跟踪每只动物(实例)吃了多少食物:

function Animal(type) {
  this.type = type;
}

// now let's say somewhere else in our program
// we want to have a farm where animals can eat.
// We want the farm to keep track of how much each animal ate but
// the animal itself doesn't need to know how much food it has eaten.
const AnimalFarm = (() => {
  const mapOfAnimalToAmountOfFood = new Map();
  return {
    feedAnimal(animal, amountOfFood) {
      // if the animal is being fed the first time
      // initialize the amount to 0
      if (!mapOfAnimalToAmountOfFood.has(animal)) {
        mapOfAnimalToAmountOfFood.set(animal, 0)
      }

      // add amountOfFood to the amount of food already eaten
      mapOfAnimalToAmountOfFood.set(
        animal,
        mapOfAnimalToAmountOfFood.get(animal) + amountOfFood
      )
    },
    getAmountEaten: function(animal) {
      return mapOfAnimalToAmountOfFood.get(animal)
    }
  }
})()

const dog1 = new Animal('dog')
const dog2 = new Animal('dog')

AnimalFarm.feedAnimal(dog1, 300)
AnimalFarm.feedAnimal(dog1, 500)
AnimalFarm.feedAnimal(dog2, 1234)

console.log(
  `dog1 ate ${AnimalFarm.getAmountEaten(dog1)} total`
)

console.log(
  `dog2 ate ${AnimalFarm.getAmountEaten(dog2)} total`
)

一般来说,创建对象到某些数据的映射的主要原因是您可以维护有关对象的本地信息,这些信息虽然与该对象直接相关,但完全包含在您自己的模块中并且不会污染任何系统的其他部分 (Separation of Concerns)。

另一个例子可能是一个图,它有一个表示节点的对象映射到与它们有连接的其他节点列表(例如在Dijkstra's algorithm 中很有用):

Map<Place, ListOfPlacesICanGoTo>

这允许您通过分离这种关系而不是在对象本身中放置直接的Place.listOfPlaces 链接来拥有更多purePlace 对象。如果在不需要 listOfPlaces 甚至没有意义的其他上下文中使用 Place,这将特别有用。

对象作为类映射结构中的键的另一种常见用法是在使用WeakMap 时,因为它还通过允许每个键对象在没有其他对象引用它时立即被垃圾收集来提高内存效率。 例如,process.on('unhandledRejection') 在节点中的底层实现,uses a WeakMap 跟踪被拒绝但没有错误处理程序在当前滴答内处理拒绝的承诺。


至于使用函数作为键,我个人认为这不太有用,但肯定不是没用的。

一个有用的例子可能是检查某个函数之前是否已经传入,而不是再次调用它,而是返回一个缓存的结果。这可以防止重复执行可能代价高昂的操作。

const map = new Map();
function invokeOrGetFromCache(fn) {
  if (map.has(fn)) {
    return map.get(fn);
  }
  const result = fn();
  map.set(fn, result);
  return result;
}

function exampleFn() {
  console.log('start');
  for (i = 0; i < 100000; i++);
  console.log('done');
  return true;
}

console.log(
  invokeOrGetFromCache(exampleFn) // runs exampleFn
);

console.log(
  invokeOrGetFromCache(exampleFn) // retrieves from cache
);

与对象一样,出于效率原因,在这些情况下使用WeakMap 也可能更可取。

【讨论】:

  • 我想我的回答只是你最后一点的一个更具体的例子。
  • 你后面的例子更有意义......但仍然没有解释为什么我会将密钥用作objectfunction 我看到你检查map.has(fn) 但你可以对 object.... 中的 prop 进行完全相同的检查,如果存在,请不要设置它。
  • @JordanDavis 将属性放入对象中,正如本答案中已经非常彻底解释的那样,它不再“纯”。如果处理不当,可能会导致内存泄漏,并且还会在使用JSON.stringify() 等进行序列化时造成副作用。
  • 我不理解您对“纯”的定义以及对象中的属性如何“混乱”,我猜....只是想了解您的意思。
  • @JordanDavis 当您有一个相当大的系统时,假设有数百个具有许多关系的对象类,使用对象到某些数据的映射允许您维护特定于对象的信息而不改变实际对象,这更安全,因为更改它可能会影响系统的许多其他部分。
【解决方案2】:

如果您编写了一个执行昂贵操作来复制/转换/包装对象或函数的函数,并且您希望该函数被多次调用以获取相同的数据,那么进行预先检查通常是一种性能改进在WeakMap 中,以确保您尚未运行昂贵的操作。

如果你有,那么你可以返回已经计算好的结果,这样可以节省很多时间。

一个真实的例子是我发布的一个名为 di-proxy 的实用程序,但为了证明我的观点,语法是这样的(在 Node.js 中):

const createInjector = require('di-proxy')
// pass dependency resolver to injector factory 
const inject = createInjector(require)
// wrap IIFE with dependency injector 
inject(({ http, express, 'socket.io': sio }) => {
  const app = express()
  const server = http.Server(app)
  const io = sio(server)
  …
})()

InternallycreateInjector() 函数将检查以确保它尚未为 require 生成包装函数。如果有,它将使用输入函数作为WeakMap 的键并返回它已经生成的包装函数,以节省时间:

function createInjector (require, noCache = false) {
  …
  // if require function is weakly referenced and memoization is enabled
  if (!noCache && this.has(require)) {
    // return cached injector
    return this.get(require)
  }
  …
  // expensive operation to generate cached injector
  …
  // weakly reference injector with require function as key
  this.set(require, inject)

  // return wrapped function
  return inject
}.bind(new WeakMap())

【讨论】:

  • 你能用 JavaScript 写一个不使用 node.js 的清晰示例吗?
  • @JordanDavis 查看第二个代码块,它涵盖了我在第一段中一般讨论的所有内容
  • 您基本上是在检查对象上是否存在属性,然后使用if/else 语句执行任何操作,我可以使用对象执行相同的逻辑,如果(prop in object)。更重要的是,将 objectfunction 作为密钥有什么好处……如果是这样,我会为它的价值赋予什么……
  • @JordanDavis 我已经回复了你的相同评论in the other answer.
【解决方案3】:

例如测试用例:

const test= new Map();

test.set('isZero', (v) => console.log(v === 0));
test.set('isNumber', (v) => console.log(typeof v === 'number'));

const value = 10;

for(let [key, fn] of test){
  // console.warn(key);
  fn(value);
}

【讨论】:

    猜你喜欢
    • 2015-12-16
    • 1970-01-01
    • 2022-06-10
    • 2018-08-06
    • 1970-01-01
    • 2019-09-25
    • 1970-01-01
    • 2018-05-13
    相关资源
    最近更新 更多