syms - quick/easy/canonical Symbol.for() access
This library is a "magic" proxy object, which generates Symbol.for()
type symbols on property access after performing a few key normalization steps, resulting in the following:
syms.foo
becomesSymbol.for("foo")
syms.TWO_WORDS
becomesSymbol.for("two.words")
syms._two_words
becomesSymbol.for("two.words")
syms.twoWords
becomesSymbol.for("two.words")
syms.TwoWords
becomesSymbol.for("two.words")
syms.kTwoWords
becomesSymbol.for("two.words")
In short, the following:
const syms = ;const kSomething ANOTHER_THING yetAnotherThing "A Fourth thing": THING4 = syms;const THING5 = syms"thing #5" // with a namespace:const example kAnotherExample EXAMPLE_THREE = symssymsns'my.app.name'
Is equivalent to:
const kSomething = Symbol;const ANOTHER_THING = Symbol;const yetAnotherThing = Symbol;const THING4 = Symbol;const THING5 = Symbol; const example = Symbolconst kAnotherExample = Symbolconst EXAMPLE_THREE = Symbol
Motivation
JavaScript Symbol
objects are an incredibly handy generic construct in modern JavaScript which can be utilized for many purposes, including:
- marking/documenting "internal" methods -- for example using
this[Symbol.for('perform-internal-action')]()
rather than something likethis._performInternalAction()
- faking classical-OO style "private" methods / properties -- While these aren't technically "private", using regular string-keyed methods/props for "public" api and symbol-keyed methods/props for "private" api is one way to separate "intended public API" vs "implementation-specific" API is an entirely valid use of Symbols in javascript
- attaching your own metadata to an object you don't control -- while this is not often considered good practice, if you ARE going to do it, it's probably safer to use a Symbol-keyed object
- "hiding" data from
JSON.stringify
--JSON.stringify
will skip symbol-keyed entries when serializing objects
There are two ways of constructing a symbol: Symbol("some-name")
or Symbol.for("some-name")
, the difference being that Symbol.for()
will always return the same symbol object when given the same key, while Symbol()
always returns a unique symbol object (in other words, Symbol("foo") !== Symbol("foo")
while Symbol.for("foo") === Symbol.for("foo")
). You can think of this library as Symbol.fuzzyFor()
.
Namespaces
In order to be clean with Symbol.for()
, it is helpful to namespace your keys with a unique prefix. For example, if you have a program named 'crayon box' and you want to define red
, green
, and blue
symbols, you may want to define these as Symbol.for('crayon.box.red')
, rather than Symbol.for('red')
, to keep your symbols from unintentionally colliding with another color management program.
A helper is provided for creating multiple symbols using the same prefix, accessed by syms[Symbol.for('ns')]("my.namespace")
or syms[syms.ns]("my.namespace")
:
const syms = ;const test = Symbol;const twoThreeFourFive: check1 = symsSymbol'one';const THREE_FOUR_FIVE: check2 = symsSymbol'one_two';const four_five: check3 = symssymsns'one :: two';const kFive: check4 = symssymsns'one/two/three'; assert;assert;assert;assert;
Why "." ?
The reason that .
is used as a word separator is because modern versions of node
use Symbol.for("nodejs.foo.bar")
style symbols, allowing things like this:
const syms = ; const sym1 = inspectcustom;const sym2 = Symbol;const NODEJS_UTIL_INSPECT_CUSTOM = syms;const nodejsUtilInspectCustom = syms; // with the `syms[syms.ns](namespaceName)` helper, you can do stuff like this:const inspectCustom = symssymsns'nodejs.util'; // sym1, NODEJS_UTIL_INSPECT_CUSTOM, and nodejsUtilInspectCustom are all the same symbol, which is also the symbol returned by ``const strict:assert = assertassert;assert;assert; { return "[[ my-thing instance ]]"; } console;
"Internal" Methods
One handy usage is to "clean up" your public/official api, without actually restricting access to the actual underlying methods/properties, which can feel slightly nicer than naming like this._internalThing
.
const SEND OUT_STREAM = ; { thisOUT_STREAM = out; } async { await ; thisSEND; } { thisOUT_STREAM; }
In the above, you could could change these vars to kSend
+kOutStream
, or
send
+outStream
, or whatever other format pleases you. You could also use
const $ = require("syms")
and use this[$.outStream]
. Consumers of a Whatever
instance (const whatevs = new Whatever({out});
) can easily call whatevs[Symbol.for("send")]()
and whatevs[Symbol.for("out stream")]
, without even having to know that
you're using this library, and it would hopefully be pretty obvious that they're
reaching into your internal / non-official / non-stable APIs and should do so at
their own risk:
const out = fs;const _send = Symbol;const _outs = Symbol;const whatever = outwhatever_send;whatever_outs;