4

In babel-preset-stage-0, we can declare static class variable as follow:

class Constants {
  static COUNTRY = Object.freeze({
      NAME: 'Germany',
      DESCRIPTION: 'placeholder',
  })
}

Is it safe to use Constants.COUNTRY as the key for a ES6 Map or Set?

eg.

const map = new Map();
map.add(Constants.COUNTRY, something);

Is it guaranteed that map.get(Constants.COUNTRY) will always return something?

Is the performance as good as using strings as key? Is it safe to use Constants.COUNTRY as eventKey (bootstrap component attribute) for NavItem too?

Is it also more appropriate to declare it as a variable instead of a class? i.e.

const Constants = Object.freeze({
  COUNTRY: Object.freeze({
      NAME: 'Germany',
      DESCRIPTION: 'placeholder',
  })
}) 
Avery235
  • 4,756
  • 12
  • 49
  • 83
  • 1
    It's guaranteed to always return your data only if the value of `Constants.COUNTRY` never changes and nobody removes that key from the map. A class static is just a variable, no different than your `Constants` variable alternative. – jfriend00 Jul 21 '17 at 03:47
  • `Object.freeze` prevents `Constants.COUNTRY` from being modified in any way, but it's possible to reassign it for the static class variable...so i guess the latter version of declaring it as a constant Object is better than declaring it as a class. – Avery235 Jul 21 '17 at 03:54
  • `Object.freeze()` prevents a change to the object (which doesn't matter for the map because it's still the same object). `Object.freeze()` doesn't prevent `Constants.COUNTRY = "hello"` which does matter when accessing `map.get(Constants.COUNTRY)`. – jfriend00 Jul 21 '17 at 03:56
  • Yeah just noticed that; declaring it as a const object is safer than as a class in light of this... – Avery235 Jul 21 '17 at 03:59
  • Yes, your second form (using `const`) would keep `Constants.Country` from being changed. I wonder if you could `Object.freeze(Constants)` in your first example. – jfriend00 Jul 21 '17 at 04:28
  • @jfriend00 Just tested it, `Object.freeze(Constants)` would prevent reassignment of `Constants.COUNTRY`, but not reassignment of `Constants`. – Avery235 Jul 21 '17 at 05:24
  • Yes, that is expected. `Constants` would need to be declared `const` to prevent reassignment as in your 2nd code block (and as described in my answer). That is the point of `const` which I assume you already know. – jfriend00 Jul 21 '17 at 05:26

2 Answers2

2

Is it guaranteed that map.get(Constants.COUNTRY) will always return something?

For map.get(Constants.COUNTRY) to always return your original value, a couple things have to be true.

  1. You have to make sure that Constants.COUNTRY can never be assigned a different value either because the .COUNTRY property was reassigned or because the Constants object was replaced with something else that had a different .COUNTRY property value.

  2. You have to make sure that nobody can ever remove that key from the map object.

If you can assure these two things, then yes map.get(Constants.COUNTRY) will always return your desired value. But, if either of those are not necessarily true, then you are not assured of always getting your value from the map.

You can assure that Constants.COUNTRY cannot be changed by freezing the Constants object or by setting that property to be configurable to it cannot be removed or written to. To assure that the Constants object cannot be replaced, it would be best for it to be const as in your second code block.

I don't know of a way to assure that nobody can call map.delete(Constants.COUNTRY) except by keeping the map object private so foreign code cannot get to it.

If you had any reason to want to prevent enumeration of the keys in the map (to make it harder for someone to discover a key perhaps), then you could use a WeakMap instead of a Map.

Is the performance as good as using strings as key?

You'd have to test a specific Javascript implementation to be sure about performance. There is no required implementation reason that one or the other should be faster - it will just depend upon the internals of the implementation.

I created a jsPerf test case to compare string lookups to object lookups. Feedback is welcome on improving how this is tested/measured, but using the current scheme where I create 10,000 string keys and 10,000 object keys in a map and then compare accessing 1000 of each, I find varying results.

Chrome is ~20% slower to access the object keys.
Firefox is ~20% slower to access the string keys.
Edge is ~27% slower to access the string keys.

Is it also more appropriate to declare it as a variable instead of a class?

As discussed, your second const form has the advantage that Constants cannot be reassigned.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Added performance measurement data on string keys vs. object keys. – jfriend00 Jul 21 '17 at 05:21
  • Just discovered a disadvantage of declaring it as `const` (to do with hoisting if I'm not wrong): I'm not able to add a new variable/function within `Constants` that refers `Constants.COUNTRY`, so I'd have to declare `COUNTRY` before `Constants` then add it to `Constants` which isn't as DRY. I guess class would be better then...anyone who decides to import a class and reassign it shouldn't be writing codes. – Avery235 Jul 21 '17 at 05:49
  • @Avery235 - `const Constants` only means that `Constants` cannot be reassigned. It has nothing to do with assigning properties to the `Constants` object. You are blocked from that because of your `Object.freeze()`. You could freeze the object after you finish building it. – jfriend00 Jul 21 '17 at 06:19
  • `class Constant { static a = 1; static b = Constant.a }` works but not `const Constant = { a: 1, b: Constant.a }`. I'd need to do `const a = 1; const Constant = { a: a, b: a }`. – Avery235 Jul 21 '17 at 06:49
  • When declaring `const Constant = {...}`, you cannot reference `Constant` inside the static declaration (since it doesn't exist yet inside the declaration). But you can declare `const Constant = {...}` and then on the following lines after that declaration add some more properties such as `Constant.a = Constant.foo`. And, then after adding all the properties you want, you could `Object.freeze(Constant)`. – jfriend00 Jul 21 '17 at 06:55
  • `class Constant { static a = 1; static b = Constant.a; static c = Constant.b }` is nicer to read than `Constant.a = 1; Constant.b = Constant.a; Constant.c = Constant.b`, keeps everything within the class block. Pretty trivial issue though... – Avery235 Jul 21 '17 at 07:00
0

You can use WeakMap to COUNTRY as key, and something as value. A variable declared with const cannot be deleted. Along with you use of Object.freeze(), wm.get(COUNTRY) should always return something

  const wm = new WeakMap;

  const COUNTRY = Object.freeze({
    NAME: 'Germany',
    DESCRIPTION: 'placeholder',
  });

  wm.set(COUNTRY, "something");

  // error when "use strict"
  delete wm;
  delete COUNTRY;

  COUNTRY.NAME = 123;

  console.log(
    wm.get(COUNTRY)
  );
  
  console.log(
    COUNTRY
  );
  
  console.log(
    wm
  );

If requirement is for a variable that cannot be deleted or changed you can use const see Is it possible to delete a variable declared using const? and JSON

"use strict";

// `Constants` cannot be changed or deleted
const Constants = `{
  "NAME": "Germany",
  "DESCRIPTION": "placeholder"
}`;

console.log(
  JSON.parse(Constants)
);

// delete Constants;
/*
Error: {
  "message": "Uncaught SyntaxError: Delete of an unqualified identifier in strict mode."
}
*/
guest271314
  • 1
  • 15
  • 104
  • 177
  • What's the advantage of a weakMap over a regular Map in this circumstance? – jfriend00 Jul 21 '17 at 04:29
  • @jfriend00 Inability to enumerate the key, if that can be described as an advantage; depends on what OP is ultimately trying to achieve with the pattern in the first instance. Also OP can use `const` instead of `class`, though that portion of Answer should not affect usage of `WeakMap` or `Map`. OP mentioned "performance", garbage collection. – guest271314 Jul 21 '17 at 04:35
  • Where was it put as a requirement or even a desire to not be able to enumerate the key from the `map`? – jfriend00 Jul 21 '17 at 04:41
  • @jfriend00 Is "advantage" within text of Answer? – guest271314 Jul 21 '17 at 04:41
  • You're offering an answer that uses `WeakMap`. I don't understand why its an interesting answer or why a `WeakMap` is more useful to answering the question than the `Map` the OP is already using. I just find it a confusing answer that does not explain itself. And `wm.delete(COUNTRY)` will make it so that `wm.get(COUNTRY)` no longer returns anything useful so your last sentence is only true with that qualifier (already mentioned in my comments, but not in your answer). – jfriend00 Jul 21 '17 at 04:43
  • @jfriend00 OP asks about "performance", "Is it also more appropriate to declare it as a variable instead of a class?". In essence the Question does not present a viable problem statement, but rather, from vantage here, how to use an object as a key, perhaps a private key. `const` can be used instead of `class`. `WeakMap` is weakly held, able to be garbage collected where no longer referenced. That is what viewed when read Question. If from your vantage the code at Question suffices for the inquiry, post an Answer to the affect. – guest271314 Jul 21 '17 at 04:49
  • @jfriend00 Is your suggestion that Answer is not helpful? – guest271314 Jul 21 '17 at 04:51
  • @jfriend00 If not being able to delete or change a variable is requirement, OP can substitute `const` for `Map` or `WeakMap` or `class`. – guest271314 Jul 21 '17 at 04:54