Symbols are by default defined as enumerable, configurable, and writable, but these will NOT pollute for/in loops, neither will show up in
Object.keys
Since native implementations already behave like this, and as behavior is very important to not break
Object.assign
logic, where only symbols explicitly defined as non enumerable will be ignored, I've decided to patch all the things so that native and polyfilled behaves the same.
TL;DR After few tests, considerations, and some brainstorming, I've decided to push get-own-property-symbols to npm and make the code available for browsers too.
I know there was already a module, but in order to fix weird problems I know and probably only I can test, and in order to add Object.getOwnPropertySymbols too, a method that MUST exist the moment we have Symbol primitive in, I thought that having an alternative instead of replacing current ES6-Symbol module would have probably been a better option.
Before Symbols
Since ES5, JavaScript objects have the ability to somehow hide properties through non-enumerability.var o = {}; Object.defineProperty(o, 'hidden', {value: 123}); for (var key in o) { // nothing will ever happen ... } Object.keys(o); // [] // empty ArrayAnother example of non enumerable property is the
length
of any Array.In order to find these "hidden" properties, we need to use Object.getOwnPropertyNames.
var o = {}; Object.defineProperty(o, 'hidden', {value: 123}); Object.keys(o); // [] Object.getOwnPropertyNames(o); // ['hidden']The convenience, when it comes to define properties in the ES5 way, is that we can always directly access a property, and simply writing it as it would be for
o.hidden
or o['any other property']
.
Introducing Symbol([description]) Basics
Directly from the MDN pageA symbol is a unique and immutable data type and may be used as an identifier for object properties.What else is unique and immutable in JavaScript? Any primitive
string
as example is, and symbols are indeed very similar to regular strings used as objects properties accessors.
var o = {}; var k = 'key'; var s = Symbol(); o[k] = 123; o[s] = 456; typeof k; // string typeof s; // symbol <== !!!If we'd like to compare normal properties with symbol properties, here a quick summary:
o[string] = value
creates a configurable, writable, and enumerable property, and same is foro[symbol] = value
, except symbol will not show up in for/in and Object.keys, but it will still be considered enumerable througho.propertyIsEnumerable(symbol)
checkObject.keys(o)
will return all enumerable properties, excluding then symbols, together with other non enumerable propertiesObject.getOwnPropertyNames(o)
will return all enumerable and non enumerable properties, still excluding symbols- both strings and symbols can be used to define properties and to retrieve properties descriptors
String() === String()
butSymbol() !== Symbol()
, and even using a descriptor, a symbol is always different from another one, unlessSymbol.for('symbol name')
is used, which is always the same symbol, providing the same label/name.
So ... What Are Symbols About ?
Here a quick list of benefits regarding the usage of symbols instead of regular strings:- symbols are great when it comes to create conflict free properties accessors, create your own Symbol in your closure and use it to set or read specific properties related to that closure
- great also for globally shared, conflicts free, libraries and utilities behaviors, if some library exposes its
Symbol.for('underscore')
, as example, every method of such library, and every plugin defined elsewhere, could eventually read the associated data - symbols have zero interferences with most common libraries, since developers can easily ignore them and these won't be on their way via common ES3 or ES5 patterns
- accordingly, symbols are usable to easily define even more hidden properties than what
enumerable: false
has done until now
WeakMap
:
function link(object, key, data) { var s = Symbol.for('@@link:' + key); return arguments.length === 2 ? object[s] : (object[s] = data); } // generic object var view = {}; // generic node link link(object, 'node', document.body); // retrieve the node any time var body = link(object, 'node');Another useful example could be a simplified
EventEmitter
constructor:
var EventEmitter = (function (s) {'use strict'; function EventEmitter() { this[s] = Object.create(null); } EventEmitter.prototype = { on: function (type, handler) { return ( this[s][type] || (this[s][type]=[]) ).push(handler) && this; }, off: function (type, handler) { return this[s][type].splice( this[s][type].indexOf(handler), 1 ) && this; }, emit: function (type, err, ok) { return this[s][type].forEach(function (h) { h.call(this, err, ok); }) || this; } }; return EventEmitter; }(Symbol('event-emitter'))); // basic test var log = console.log.bind(console); var e = (new EventEmitter) .on('log', log) .emit('log', Math.random()) .off('log', log);Above example is probably the tiniest emitter implementation I could imagine ... and all thanks to
Symbol
, how sweet is that?You can also implement a WeakMap polyfill in few lines of code.
Polyfill Caveats
Well, the first caveat is thatObject.create(null)
dictionaries, as well as those created via {__proto__:null}
, will not work as expected. Symbols would be set as generic keys in there so, in case you need symbols, be aware of this limit and bear in mind all other shimms and polyfills have same limit.The other biggest inconsistency with native
Symbol
is that typeof
will return string
and not symbol
.If we need to perform such check, here a little utility that can help:
var isSymbol = (function (cavy) { return function isSymbol(s) { switch (typeof s) { case 'symbol': return true; case 'string': cavy[s] = 1; var isit = Object.getOwnPropertyNames(cavy).indexOf(s) < 0; delete cavy[s]; return isit; default: return false; } }; }({})); isSymbol(''); // false isSymbol(Symbol()); // trueThese two are the most inconsistent points I have but the good news is: yayyyyyy, we've got Symbols in basically every bloody browser and JS engine!
No comments:
Post a Comment