My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Friday, March 29, 2013

Simulating ES6 Symbols In ES5

Symbols, previously known as Names, are a new way to add real private properties to a generic object.
// basic Symbol example
var BehindTheScene = (function(){
  var symbol = new Symbol;

  function BehindTheScene(){
    this[symbol] = {};
  }

  BehindTheScene.prototype.get = function(k) {
    return this[symbol][k];
  };

  BehindTheScene.prototype.set = function(k, v) {
    return this[symbol][k] = v;
  };

  return BehindTheScene;

}());

var obj = new BehindTheScene;

obj.set('key', 123);

obj.key; // undefined
obj.get('key'); // 123
In few words symbol makes possible to attach properties directly without passing through a WeakMap. A similar behavior could be obtained indeed via WeakMaps:
// similar WeakMap example
var BehindTheScene = (function(){
  var wm = new WeakMap;

  function BehindTheScene(){
    wm.set(this, {});
  }

  BehindTheScene.prototype.get = function(k) {
    return wm.get(this)[k];
  };

  BehindTheScene.prototype.set = function(k, v) {
    return wm.get(this)[k] = v;
  };

  return BehindTheScene;

}());

var obj = new BehindTheScene;

obj.set('key', 123);

obj.key; // undefined
obj.get('key'); // 123

Why Symbols

To be honest I am not sure but these somehow bring some magic to the object rather than wrapping magic around it, as it is for the WeakMap example, so at least performance should be better ... right? Well, the current shim VS shim says that indexOf() is faster than an implicit toString(): check this test out by yourself ;)
In any case it looks like private symbols will be a better way to go than WeakMap when all we would like to have is a private property. Symbols can be used as Enums too, being unique as any other object is.

Simulating Symbols In Current ES5 JavaScript

As easy as this:
var Symbol;
if (!Symbol) {
  Symbol = (function(Object){

    // (C) WebReflection Mit Style License

    var ObjectPrototype = Object.prototype,
        defineProperty = Object.defineProperty,
        prefix = '__simbol' + Math.random() + '__',
        id = 0;

    function get(){/*avoid set w/out get prob*/}

    function Symbol() {
      var __symbol__ = prefix + id++;
      defineProperty(
        ObjectPrototype,
        this._ = __symbol__,
        {
          enumerable: false,
          configurable: false,
          get: get, // undefined
          set: function (value) {
            defineProperty(this, __symbol__, {
              enumerable: false,
              configurable: true,
              writable: true,
              value: value
            });
          }
        }
      );
    }

    defineProperty(Symbol.prototype, 'toString', {
      enumerable: false,
      configurable: false,
      writable: false,
      value: function toString() {
        return this._;
      }
    });

    return Symbol;

  }(Object));
}
A very basic example here:
var sym = new Symbol;
var o = {};
o[sym]; // undefined

o[sym] = 123;
console.log(o[sym]); // 123
for (var k in o) {
  console.log(k); // nothing at all
  // there is nothing to for/in
}
delete o[sym]; // true
Of course, you can try also the very first example, the one with a shared, private, symbol variable, that will simply work as expected :)
Bear in mind, regardless being a hack, this script does not actually cause any problem to any other script or library you are using today but it needs ES5 compatible browsers such all mobiles plus all desktops and IE9 or greater.

9 comments:

DBJDBJ said...

I can't see why do I need Symbol?
[js]
// basic Symbol example without a Symbol
var BehindTheScene = (function(){

var symbol = {};
function BehindTheScene(){
this[symbol] = {};
}
BehindTheScene.prototype.get = function(k) {
return this[symbol][k];
};
BehindTheScene.prototype.set = function(k, v) {
return this[symbol][k] = v;
};
return BehindTheScene;
}());
var bts = new BehindTheScene ;
bts.set('A',2);
bts.get("A");
/*
2
*/
[/js]
Perhaps my ES5 is a bit rusty ?

Andrea Giammarchi said...

I didn't invent Symbols in ES6 so don't ask me but yeah, your example does not make sense becaus eyou cannot have 2 different symbols.

You are simply using '[object Object]' as instance key plus that's enumerable and not private at all ... so yes, you did not get much about Symbols and the fact I've hacked ES5 to have them now

Christopher said...

I saw @benvie's implementation a while back which was cool too, but yours is a lot more readable/compact. Kudos!

DBJDBJ said...

... one can have "two different symbols" with the example I did ?

DBJDBJ said...

nut I can have "two different symbols" with my example.

DBJDBJ said...

Also here is a version (of course) which keeps symbol storage completely hidden:
// basic Symbol example without a Symbol
var BehindTheScene = (function(){

var symbol = {};
function BehindTheScene(){
symbol = {};
}
BehindTheScene.prototype.get = function(k) {
return symbol[k];
};
BehindTheScene.prototype.set = function(k, v) {
return symbol[k] = v;
};
return BehindTheScene;
}());
var bts = new BehindTheScene ;
bts.set('A',2);
bts.set('B',3);
document.writeln(bts.get("A"));
document.writeln(bts.get("B"));

DBJDBJ said...

An working example without any Symbol lib

@Andrea you are not sure why ES6 Symbol , but then you go on and 'hack it' in ES5. Please discuss ?

Andrea Giammarchi said...

DBJDBJ I simply showed how Symbols are... and you don't need a closure to simulate them. You are stuck with a usage example, not with symbols and yes, your example won't work with more than a instance, mine will

DBJDBJ said...

Ah sorry, I was "coding" on Galaxy S2, so please ignore (delete) my previous posts here ;o) Of course here is *extremely* simple code that does whatever you/me/we think are the requirements for ES6 Symbol.

I am addressing ES6 not you ... My example works and yours does too .. so what is the purpose of adding Symbols to ES6 ? Perhaps you can see it, but I can not ...