Search code examples
javascriptjavascript-objects

Why can't I use a function as key of a Map?


Consider two different function objects created by the same expression or declaration (i.e. by the same line of code). In this example (which you can run), those functions are wa and wb, created by the same expression in the wrap function:

function a() { return "abc" }
function b() { return 123 }
function wrap(f) { return () => f() }
var wa = wrap(a)
var wb = wrap(b)

Any such two functions

  • will not compare equal according to ===,
  • will not compare equal according to Object.is,
  • but will be treated as equal keys by Map.

MDN says:

Key equality is based on the sameValueZero algorithm: NaN is considered the same as NaN (even though NaN !== NaN) and all other values are considered equal according to the semantics of the === operator.

You can read their definitions of same-value-zero and same-value as well. By my reading, two different function objects should be two different keys.

(Objects exhibit the same behavior, but because they aren't meant to support keys that aren't strings or symbols, I am giving them a pass. I am tagging them because there is no tag for Maps.)


Solution

  • You're not setting the Map key correctly - you're treating the Map as an ordinary object, and putting keys on an ordinary object results in the keys being cast to strings. So

    var m = new Map()
    m[wa] = "hello, world!"
    m[wb]
    

    coerces the wa and wb to strings (because object keys may only be strings), and the above code resolves to

    var m = new Map()
    m["() => f()"] = "hello, world!"
    m["() => f()"]
    

    To use the Map properly (so that it can have keys of any type, including functions and objects, that don't get coerced to strings), use Map.set and Map.get instead. Then, the behavior will be as expected - the two separate functions, not being === to each other, will have different keys in the Map:

    function a() { return "abc" }
    function b() { return 123 }
    function wrap(f) { return () => f() }
    var wa = wrap(a)
    var wb = wrap(b)
    
    
    const m = new Map();
    m.set(wa, 'Hello World');
    console.log(m.get(wb)); // undefined; the map sees wa and wb as different, as expected