Dean Tribble (via Rob Palmer) has come up with a neat trick to create constants whose values are strings with their names. With a minor change, we can use it to set up symbol-valued constants.
const SymbolFactory = new Proxy({}, {
get: (_target, propKey, _receiver) => Symbol(propKey) // (A)
});
const {red, green, blue} = SymbolFactory; // (B)
assert.equal(
typeof red, 'symbol'
);
assert.equal(
String(red), 'Symbol(red)'
);
Each property of the pattern that object-destructures SymbolFactory
, triggers a property get
operation. Therefore, line B is equivalent to:
const red = SymbolFactory.red;
const green = SymbolFactory.green;
const blue = SymbolFactory.blue;
The method in line A intercepts each of these get
operations. Among other information, it is provided with the key of the relevant property. It then creates and returns a symbol whose description is the property key.
You might think that we should cache the results so that every time we use a name, we get the same symbol. We can do that via a Map
(we can’t use a WeakMap
because its keys must be objects):
const cache = new Map();
const SymbolFactory = new Proxy({}, {
get: (_target, propKey, _receiver) => {
let result = cache.get(propKey);
if (result === undefined) {
result = Symbol(propKey);
cache.set(propKey, result);
}
return result;
}
});
Or we can use Symbol.for()
:
const SymbolFactory = new Proxy({}, {
get: (_target, propKey, _receiver) => Symbol.for(propKey)
});
However, each invocation producing a fresh symbol is a feature, not a bug – for example:
// colors.mjs
const {red, green, blue} = SymbolFactory;
// mood.mjs
const {happy, blue} = SymbolFactory;
We want each blue
to be unique!
We could cache and always access factory results via properties of SymbolFactory
, but then we aren’t protected against typos and basically get one big enum.