New primitive since ES6, 'Symbols are all about Reflection within implementation - you sprinkle them on your existing classes and objects to change the behaviour.'
New primitive: typeof Symbol() === 'symbol'
Create a symbol:
- Symbol() - call the Symbol function (Error: new Symbol() is wrong, it's not a constructor)
- Symbol('a description') - the description is used for debugging, i.e. when logging them to console
Symbols can be used as object keys and give an 'alternative' layer to objects:
- someObject[someSymbol] - retrieve the item
- They don't conflict with other symbols - they are COMPLETELY UNIQUE (even with same description)
- They don't conflict with string keys
- They don't show up in:
- 'for in' loop
- 'for of' loop
- Object.getOwnPropertyNames
- They only show up in:
- Object.getOwnPropertySymbols
Symbol.for('aStringKey') : creates a Symbol in a 'global registry' for fetch & reuse with a string key:
- cross-realm (meaning cross iframe or service worker)
Symbol.keyFor(someSymbol):
- undefined - if not created with Symbol.for
- the string key - if created with Symbol.for with the key
Good for:
- extending objects being given (by others, by a library, etc.) without affecting the object (your symbol is unique),
- like a 'safe attachment', would not show up in normal iteration, good to place metadata in object
- whenever a unique value is needed (traditionally when string / number is used) - benefit: change description won't affect the uniqueness
- a library can export some Symbol constants, then user of the library can use the Symbol as property key to add hooks to some objects - since Symbols are unique, it won't confuse others (so libraries won't contend for the same property key): this is a real issue with console.log method, which uses 'inspect' as key to find custom hook, and could conflict with someone accidentally defined an 'inspect' method for other purpose
- "well known symbols", see below
Not good for:
- hiding something? all symbols are public
- Object.assign() will copy the symbols as well! If that's not desired, use Object.defineProperty to make it non-enumerable
- Symbol don't coerce into primitive, won't mix with string
Well known symbols (that alters low level behavior)
Symbol.hasInstance - drives 'instanceof' for ES6 compliant engine, so:
- 'o1 instanceof 2' would call 'o2[Symbol.hasInstance](o1)'
Symbol.iterator - drives 'for of' loop:
- arrayOrArrayLikeObject[Symbol.iterator] - should be a generator function that creates iterator
- allows 'for of' loop with that iterator
Symbol.isConcatSpreadable - drives Array.concat:
- [].concat(anotherArray) will flatten the added array (add members, not the array)
- true/false: controls spread or not
Symbol.unscopables - too special usage, ignore...
String methods drivers:
- Symbol.match - drives String.match
- Symbol.replace - drives String.replace
- Symbol.search - drives String.search
- Symbol.split - drives String.split
Symbol.species - for some objects such as an array [], constructor[Symbol.species] points to its constructor ([].constructor[Symbol.species] === [].constructor // true). However unlike 'constructor', this can be changed so to instruct some functions to not to use child class...
Symbol.toPrimitive - set a function that takes a hint (type of result) and do a conversion. drives behavior when needs to convert object to primitive
Symbol.toStringTag - attach a tag to be used in the object's toString(), for debugging and also 'chai' uses it