Set your Setter and Get your Getter

Getters and Setters are a lot of fun to use. Unfortunately not all browsers implement them. According to John Resig’s post JavaScript Getters and Setters only two browsers currently support them (Opera 9.5 has since been release so make it three). Even before I was really into Javascript programming I used PHP’s Magic Methods __set and __get.

It is with much happiness that I think I can finally say, without seeming like a fool, that: “JavaScript Getters and Setters are now prevalent enough to become of actual interest to JavaScript developers.” Wow, I’ve been waiting a long time to be able to say that.

John Resig – JavaScript Getters and Setters

As much as I would love this to be true, I still do not quite think we are there. Rhino adding support was good, because with that John was able to implement window.location in env.js

So now that we have some background on setters and getters in Javascript. Here is my implementation for browsers that do not already support it.

                        (function()
                        {
                                var gettysetty =
                                {
                                        clock: 0,
                                        gets: Array(),
                                        sets: Array()
                                }; 

                                function procVars()
                                {
                                        // run through all the getters, update the var from the function
                                        for(var i = 0; i < gettysetty.gets.length; i++)
                                                gettysetty.gets[i][0][gettysetty.gets[i][1]] = gettysetty.gets[i][2](); 

                                        // same thing here, but instead check that the var has not changed, if it has, run the setter func
                                        for(i = 0; i < gettysetty.sets.length; i++)
                                                if(gettysetty.sets[i][0][gettysetty.sets[i][1]] != gettysetty.sets[i][2]["ogVar"])
                                                {
                                                        gettysetty.sets[i][2].apply(gettysetty.sets[i][0], [ gettysetty.sets[i][0][gettysetty.sets[i][1]] ]);
                                                        gettysetty.sets[i][2]["ogVar"] = gettysetty.sets[i][0][gettysetty.sets[i][1]];
                                                }
                                } 

                                var getterName = (Object.__defineGetter__ != "undefined") ? "defineGetter" : "__defineGetter__";
                                Object.prototype[getterName] = function(v, f)
                                {
                                        this[v] = f(); 

                                        gettysetty.gets.push([this, v, f]); 

                                        if(!gettysetty.clock)
                                                setInterval(procVars, 13); 

                                        return this;
                                } 

                                Object.prototype[(Object.__defineSetter__ != "undefined") ? "defineSetter" : "__defineSetter__"] = function(v, f)
                                {
                                        f["ogVar"] = this[v];
                                        gettysetty.sets.push([this,v,f]);
                                        if(!gettysetty.clock)
                                                setInterval(procVars, 13);
                                }

                                function lookup(gs, v)
                                {
                                        for(var i = 0; i < gettysetty[gs].length; i++)
                                                if(gettysetty[gs][i][0] == this && gettysetty[gs][i][1] == v)
                                                        return gettysetty[gs][i][2];
                                                else if(gettysetty[gs][i][0] == this)
                                                        return;
                                        return;
                                }

                                Object.prototype[(Object.__lookupSetter__ != "undefined") ? "lookupSetter" : "__lookupSetter"] = function(v)
                                {
                                        return lookup.apply(this, ["sets", v]);
                                };

                                Object.prototype[(Object.__lookupGetter__ != "undefined") ? "lookupGetter" : "__lookupGetter"] = function(v)
                                {
                                        return lookup.apply(this, ["gets", v]);
                                };
                        })();

                        var x = {};
                        x.defineGetter("time", function()
                        {
                                return (new Date()).valueOf();
                        });

                        x.defineSetter("x", function(val)
                        {
                                this["x"] = val+1;
                                alert("new value: " + val);
                        });

The code here is fairly simple. When the first getter or setter is define a timer is started. It iterates through all the defined getters and executes their function updating the value it is responsible for. Then it iterates the setters, checking if the current value is different from value it had when set. If they are different it runs then setters function and updates the ‘old’ variable to reflect the new one.

This only supports adding by the Object methods, so you cannot define them in an object notation. As for the future, v8 supports them, and I looks like Internet Explorer 8 may support some variation in some very Microsoft way. defineProperty anyone? While this solution is far from bullet proof and probably will scale terrible it could possibly work as an absolute fallback.

Leave a Comment