Back

Meta-programming JavaScript Using Proxies

Javascript, Meta-Programming |

Thew new Proxy api presented in ecmascript6 allows us to do some really sick stuff to JavaScript’s syntax, which in my point of view is great.
I love meta-programming and up until now it was an extremely problematic/hack-ish thing to in JavaScript.

If you want to try out the code-snippets I will post in this article, you will have to use Chrome/Chromium and enable the “Experimental JavaScript” flag in your browser.

You can do it simply by typing chrome://flags/ in your address bar and CTRL+F-ing “Experimental JavaScript”.

Proxies allow us to define JS objects in a way that were impossible before.
Below is a small example made by Mozilla:

var proxy;

var handler = {  
    has: function (name) {
        return name == 'foo';
    },
    get: function (rcvr, name) {
        if (name != 'foo')
            return undefined;
        print(proxy !== rcvr);
        return "bye";
    },
};

proxy = Proxy.create(handler);

var c = Object.create(proxy);

print(c.foo); // prints: 'true' (from "proxy !== rcvr") and 'bye' (from "c.foo").  
// In this example, the get trap rcvr argument is the c object while proxy is its prototype

Implementing Pipes

After fiddling around with the Proxy API myself, I was able to get some pretty cool results.
First thing I wanted to try is to implement pipes in JS.

I already done it once using pseudo operator overloading (you can see the result here but I wanted to see how easy it will be with proxies. The answer is “pretty damn easy”.
You can see the result below:

var pipe = (function () {

    var pipe;

    return function (value) {
        pipe = [];
        return Object.create(Proxy.create({
            get: function (pipeObject, fnName) {
                if (fnName == "get")
                    return pipe.reduce(function (val, fn) { return fn(val); }, value);

                pipe.push(window[fnName]);
                return pipeObject;
            }
        }));
    }
}());

var double        = function (n) { return n*2 },  
    pow           = function (n) { return n*n },
    reverseInt    = function (n) { return n.toString().split('').reverse().join('')|0 };


var pipedExample = pipe(3) . double . pow . reverseInt . get  
console.log(pipedExample); //63  

Well that’s cool, but how far can you take it?

As far as overloading getters on objects allows us, which is pretty far.
Using Proxies I was able to create a query like syntax for filtering repositories represented in JSON.
Here is the result:

var ChristmasList = Repository([

    {
        name: "Daniel",
        age: 12,
        wants: "a dog",
    },


    {
        name: "Julia",
        age: 8,
        wants: "a bottle of rum"
    },


    {
        name: "Vitaly",
        age: Infinity,
        wants: "a dog"
    },

    {
        name: "Ina",
        age: 20,
        wants: "Vitaly"
    }
]);


ChristmasList.wants['a dog'].get; // [ {age: 12, name: "Daniel",wants: "a dog"}, {age: Infinity, name: "Vitaly", wants: "a dog"} ]  
ChristmasList.age['<']['12'].get; // {name: "Julia", age: 8, wants: "a bottle of rum"}  
ChristmasList.wants['a dog'].age[">"]["12"].get; // {name: "Vitaly", age: Infinity, wants: "a dog"};  
ChristmasList.wants[  
    ChristmasList.wants['a dog'].age[">"]["12"].get.name
].get // {name: "Ina", age: 20, wants: "Vitaly"}

For the implementation that allowed the code above to happen, feel free to check the Github repository.
Don’t forget to star it and feel free to fork it (: