One more Sunday post and another attempt to hide weird looking, overcomplicated code under the “hacking” label. That’s what we use it for right? Maybe not but here it goes!

Intro - Metaprogramming

I am not gonna give a (sucky) definition of what metaprogramming is, instead i’ ll leave some links:
Interesting StackOverFlow thread
Some wikipedia
What is homoiconicity(a good read!)

I got my initial feel on what all these people going on about through ruby with help from these two excellent resources:
Dave Thomas’ Screencast: The Ruby Object Model and Metaprogramming
Metaprogramming Ruby 2

My take on javascript’s metaprogramming abilities

Javascript gives us a lot of freedom to inspect our program at runtime and even modify almost any core aspect of the environment.
In fact these operation can be taken to such an unimaginable level of control that could completely destroy any kind of semantics and standard behavior(check this!). I really dig that, a lot of discipline is needed of course but i believe understanding these kind of things and more importantly when or not to use them really improves our out of the box thinking and creativity.

So, what’s wrong then?
Using these techniques without bringing total calamity for your program with eval() ( or eval()-like ) operations, dirty checking loops(as you will see below) and more.
ES6 brings new features that improve the situation a lot!
Proxies are on of them and i will some time soon write a part 2 of this post using them.
A great explanation for proxies and metaprogramming in general can be found in chapter 28. Metaprogramming with proxies from the book Exploring ES6 by Dr. Axel Rauschmayer, must read(and free to read online!). Also i have to recommend his previous book Speaking JavaScript as a complete javascript book(ES5).

Overwriting localStorage

Recently, there was a need for our internal testing tool to provide a kind of “session-like” behavior on localStorage values(but keeping them on localStorage, not on session) and without modifications on the core program.
So, i thought i could auto-append a specific identifier(session id, timestamp, whatever) in front of the localStorage keys and auto-remove it for retrieval, here is how i did it.

First step: Overwriting setItem() and getItem()

//our identifier to be added in front of the localstorage keys
var myId = "myIdentifier__"

//save the original functions
var _setItem = Storage.prototype.setItem;
var _getItem = Storage.prototype.getItem;

//overwrite their function bodys forcing javascript to create new function copies
Storage.prototype.getItem = function(item) {
  //add the identifier infront of the key
  item = myId + item
  //and call the original function
  return _getItem.call(this, item)
}
Storage.prototype.setItem = function(key, val) {
  //add the identifier infront of the key
  key = myId + key
  //and call the original function
  _setItem.call(this, key, val)
  //return the value to comply with the standard behaviour
  return val
}

Nothing weird here.
But here is an interesting trivia: Functions in javascript are objects, so saving them to another variable doesn’t make a copy of it but instead just passes the reference.
So you usually have to clone them(for functions var _myFunc = myFunc.bind({}) is a trick i like), but when you modify the function body you are actually forcing javascript to create a new copy of the function and the original remains intact(read more here), cool stuff!

So now we end up with something like this:

localStorage.setItem("greeting", "hello") // becomes "myIdentifier__greeting": "hello" on our storage
//but you can retrieve it normally
localStorage.getItem("greeting") // => "hello"

What about the alternative forms: localStorage.myKey= and localStorage.myKey ?

This is where it gets a little messy. There might be better ways but this is what i came up with:
In an interval of 100ms check for new keys, re-save them with a modified key, keep track of the modification, remove the original and create a getter for retrieval.
Here is what it looks like:

//dirty checking loop every 100ms
setInterval(function() {
  for (var prop in localStorage) {
    //if we have a new key or an existing key has an updated value
    if(localStorage.hasOwnProperty(prop) && ((prop.indexOf(myId) === -1 && !watchList[prop]) ||
      (prop.indexOf(myId) === -1 && watchList[prop] && localStorage[prop] !== watchList[prop]))) {
      console.log("Saving " + prop + " = " + localStorage[prop] + " as " + myId + prop + " = " + localStorage[prop]);
      //add it to the watchlist
      watchList[prop] = localStorage[prop]
      //use our modified setter to add it with an identifier on the front
      localStorage.setItem(prop, watchList[prop])
      //remove the original
      localStorage.removeItem(prop)
      //define a custom getter that points to our modified getItem
      Storage.prototype[prop] = localStorage.getItem(prop)
    }
  }
}, 100)

And here is one more detail, on page reload we need to re-track them and re-define the getters:

//on reload we need to re-track our keys and re-define their getters
var watchList = {}
for (var prop in localStorage) {
  //if it's not tracked
  if(localStorage.hasOwnProperty(prop) && prop.indexOf(myId) > -1 && !watchList[prop]) {
    //add it to the watchlist with its original key name
    watchList[prop.replace(myId, "")] = localStorage[prop]
    //define a custom getter that points to our modified getItem
    Storage.prototype[prop.replace(myId, "")] = localStorage.getItem(prop.replace(myId, ""))
  }
}

Here is a complete gist

Side Note/Update: As people pointed on reddit you can use the storageEvent to respond to changes instead of the setInterval(). What i forgot to mention is that this was made for a testing tool that needed to target IE 9 and up and unfortunately these events are completely broken on IE 10/11.
So if you don’t care about IE please use the events.

Conclusion

It works fine for my usecase, not the most elegant piece of code but it fits its purpose(looking forward to rewrite it with proxies!).
Give it a try, mess with localStorage or anything else you thought was impossible!
Have fun!