title: Extend primitives without prototyping them
body: <p>I am working on a pretty ugly library that lets you do some strange things.
  Having a graph you can map a set of collections in a chain-like style and when you alter a
  value to be altered in the whole system.</p>
  
  <p>The problem came when the end type is a JS primitive.</p>
  
  <p>In my case after making the graph with the values and objects I can do something like this:</p>
  
  <pre><code>CHAIN.components[0].value = 20; 
  </code></pre>
  
  <p><code>components</code> is a filter function over the graph's nodes using setters and getters.
  If there is only one node filtered in components the default value set by the user would be available without doing this:
      CHAIN.components.value = 20;
  But rather this:
      CHAIN.components = 20;</p>
  
  <p>Now the problem is that the node could have other methods or properties besides a default ( which in my case is set on <code>value</code>.</p>
  
  <p>How can i use the setters and getters on Number object without hacking in the Number.prototype, because <code>CHAIN.components</code> is now a Number ( if it's not a primitive i've made it work in an unobtrusive way ), but when i want to call the <code>CHAIN.components.func()</code> there is a problem because i would have to append to the Number.prototype the <code>func</code> every time i make a set or get on the <code>components</code> and then delete it.</p>
  
  <p>Do you have another idea for accomplishing this kind of behavior?</p>
  
  <p>You wanted code so here it is: </p>
  
  <pre><code>/*jslint nomen: true, sloppy: true*/
  GRID.modules.OHM || Object.extend(GRID.modules, ( function() {
      var Node, Nodes, Ohm, num_proto = Number.prototype.__clone(), str_proto = String.prototype.__clone();
      Node = function(uid) {
          var UID = uid;
          this.getUID = function() {
              return UID;
          };
      };
      Nodes = function() {
          var stack = [];
          this.add = function(id, val) {
              var n = new Node(stack.length);
              val.id = id;
              Object.extend(n, val);
              stack.push(n);
              return n.getUID();
          };
          this.getById = function(id) {
              return stack.filter(function(v) {
                  var a = id || v.id;
                  return (v.id === a);
              });
          };
          this.getByUID = function(UID) {
              return stack[UID];
          };
          this.get = function(callback) {
              !Object.isString(callback) || ( callback = [callback]);
              var f = Object.isFunction(callback) ? callback : (Object.isArray(callback) ? function(k) {
                  return (callback.indexOf(k.id) &gt;= 0);
              } : function(k) {
                  return true;
              });
              return stack.filter(f);
          };
      };
      Ohm = function(n) {
          var graph = n || (new Nodes()), filters = {}, __nodes = {}, addGS = function(obj, name, conf, binder) {
              var alfa = {};
              Object.extend(alfa, conf);
              if (!alfa.get) {
                  alfa.get = function() {
                      var a = this.g.getById(this.p);
                      return a.length === 1 ? a[0] : a;
                  }.bind(binder);
              } else {
                  alfa.get = alfa.get.bind(binder);
              }
              if (!alfa.set) {
                  alfa.set = function(value) {
                      this.g.getById(this.p).forEach(function(k) {
                          Object.extend(k, value);
                          return true;
                      });
                  }.bind(binder);
              } else {
                  alfa.set = alfa.set.bind(binder);
              }
              Object.defineProperty(obj, name, alfa);
          }, add = function(id, node) {
              if (__nodes.hasOwnProperty(id)) {
                  addGS(__nodes, id, {
                      enumerable : true
                  }, {
                      t : this,
                      p : id,
                      g : graph
                  });
              }
              return graph.add(id, node || {});
          };
          Object.extend(this, {
              add : function() {
                  add.apply(this, arguments);
              },
              map : function(name, f, that) {
                  var n = name, filterer = ['add', 'map', '__all'];
                  n = Object.isFunction(n) ? name.apply(that, arguments.slice(3)) : n;
                  if (filterer.indexOf(n.toLowerCase()) &gt;= 0) {
                      console.log("You can't map over a basic property of object !!! Please read the freakin' manual.");
                      return null;
                  }
                  if (!filters.hasOwnProperty(n)) {
                      filters[n] = new Ohm(graph);
                      addGS(this, n, {
                          get : function() {
                              this.g.get(this.f).forEach(function(v, key, arr) {
                                  var temp, binder;
                                  if (arr.length !== 1) {
                                      if (!this.filt.hasOwnProperty(v.id)) {
                                          addGS(this.filt, v.id, {
                                              set : function(value) {
                                                  this.t.g.getById(this.p).filter(this.t.f).forEach(function(k) {
                                                      Object.extend(k, value);
                                                  });
                                              },
                                              get : function() {
                                                  var a = this.t.g.getById(this.p).filter(this.t.f);
                                                  return a.length === 1 ? a[0] : a;
                                              }
                                          }, {
                                              t : this,
                                              p : v.id
                                          });
                                          (key !== arr.length - 1) || Object.extend(this.filt, this.g.get(this.f));
                                      }
                                  } else {
                                      if (Object.isFunction(v.__new__)) {
                                          v.__default = function() {
                                              return Object.extend((new this.__new__(arguments)), this);
                                          };
                                      }
                                      if (!Object.isUndefined(v.__default)) {
                                          temp = this.filt;
                                          this.filt = Object.isFunction(v.__default) ? v.__default.bind(v) : v.__default;
                                          if (Object.isNumber(this.filt) || Object.isString(this.filt)) {
                                              var prot = Object.isNumber(this.filt) ? Number : String;
                                              for (var i in temp) {
                                                  if (temp.hasOwnProperty(i) &amp;&amp; !prot.prototype.hasOwnProperty(i)) {
                                                      var bin = {
                                                          t : temp,
                                                          m : i,
                                                          p : prot,
  
                                                      };
                                                      Object.defineProperty(prot.prototype, i, {
                                                          set : function(value) {
                                                              Object.defineProperty(this.p.prototype, this.m, {
                                                                  configurable : true, // defaults to false
                                                                  writable : false,
                                                                  value : 1
                                                              });
                                                              delete this.p.prototype[this.m];
                                                              this.t[this.m] = value;
                                                          }.bind(bin),
                                                          get : function() {
                                                              Object.defineProperty(this.p.prototype, this.m, {
                                                                  configurable : true, // defaults to false
                                                                  writable : false,
                                                                  value : 1
                                                              });
                                                              delete this.p.prototype[this.m];
                                                              return this.t[this.m];
                                                          }.bind(bin),
                                                          enumerable : true,
                                                          configurable : true
                                                      });
                                                  }
                                              }
                                          } else {
                                              Object.extend(this.filt, temp);
                                          }
                                      }
                                      if (Object.isNumber(this.filt) || Object.isString(this.filt)) {
                                          var prot = Object.isNumber(this.filt) ? Number : String;
                                          for (var i in v) {
                                              if (v.hasOwnProperty(i) &amp;&amp; !prot.prototype.hasOwnProperty(i)) {
                                                  var bin = {
                                                      t : v,
                                                      m : i,
                                                      p : prot,
  
                                                  };
                                                  Object.defineProperty(prot.prototype, i, {
                                                      set : function(value) {
                                                          Object.defineProperty(this.p.prototype, this.m, {
                                                              configurable : true, // defaults to false
                                                              writable : false,
                                                              value : 1
                                                          });
                                                          delete this.p.prototype[this.m];
                                                          this.t[this.m] = value;
                                                      }.bind(bin),
                                                      get : function() {
                                                          Object.defineProperty(this.p.prototype, this.m, {
                                                              configurable : true, // defaults to false
                                                              writable : false,
                                                              value : 1
                                                          });
                                                          delete this.p.prototype[this.m];
                                                          return this.t[this.m];
                                                      }.bind(bin),
                                                      enumerable : true,
                                                      configurable : true
                                                  });
                                              }
                                          }
                                      } else {
                                          Object.extend(this.filt, v);
                                      }
                                  }
                              }, this);
                              return this.filt;
                          },
                          set : function(value) {
                              this.g.get(this.f).forEach(function(k) {
                                  Object.extend(k, value);
                              });
                          }
                      }, {
                          t : this,
                          f : f,
                          g : graph,
                          filt : filters[n]
                      });
                  }
              }
          }, true, true);
          addGS(this, '__all', {
              get : function() {
                  var a = this.g.getById();
                  Object.extend(__nodes, a.length === 1 ? a[0] : a);
                  return __nodes;
              },
              enumerable : true
          }, {
              t : this,
              p : null,
              g : graph
          });
      };
      window['Ω'] = Ohm;
      return {
          OHM : Ohm,
      };
  }()));
  </code></pre>
  
  <p>And now the demo:</p>
  
  <pre><code>var c = new Ω();
  c.add('ann', {
  __default : 58,
  blah : 98,
  ceva : function()
  {
      console.log('asd');
  }
  });
  c.add('ann2',{
      __default: function(){
         console.log('hello');
      },
      abc: 78,
      dce: function(){
         console.log(' world');
      }
  };
  c.add('b2', {
  __new__ : function() {
      this.init = function() {
          this.id = 86;
      };
      this.mer = function() {
          console.log(this);
      };
  },
  els : 'asadar'
  });
  c.map('b2', function(k) {
  return k.id === 'b2';
  });
  c.map('ann', function(k) {
  return k.id === 'ann';
  });
  c.map('ann2', function(k) {
  return k.id === 'ann2';
  });
  console.log(c.ann); // returns 58 ( the __default value )
  console.log(c.ann.blah); // returns 98
  console.log(c.ann.blah.blah); // undefined
  console.log(c.ann2);  // function()
  c.ann2(); // prints out 'hello'
  c.ann2.cde(); // prints out 'world'
  c.ann2 = 60;
  console.log(c.ann2); // 60
  console.log(c.ann2.cde()); // prints out 'world'
  </code></pre>
  
  <p>This code works, but the part where i have to use the Number or String prototype bothers me. Do you have another way of doing this?</p>
  
  <p>The reason is to do something that someone said it could be done in PHP but not JS, this guy had recently been working with me on WebGL shaders and hated that he had to write 700 lines of code to use multiple effect combined with FBO instead of 100 which would took him with a similar tool like this one which was written in PHP. So yes i KNOW the accessors on primitive prototypes is a hack but how can i make it different without having to use valueOf if the chain end object is a primitive?</p>