GitHub LinkedIn RSS
Sunday, May 25, 2014

Getters and Setters in JavaScript


This is the fourth article in the series of Object Oriented Javascript. Following our discussion on inheritance and it's integration with modular design pattern, I'd like take a deeper look into encapsulation and usage of mutator methods. Getters and setters allow you to build useful shortcuts for accessing and mutating data within an object.

__defineGetter__ and __defineSetter__


If you had searched for the information over the internet, you would have probably encountered recommendations, mostly from Microsoft :), to use __defineGetter__ and __defineSetter__. You may have also found some hacks for IE7 and even IE6. Even from a look at them you can smell something fishy. JavaScript is not C and doesn't encourage usage of underscores. Your gut feeling is right.
This feature has been removed from the Web standards. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.
For the aforesaid reasons, I'm not going even to discuss these methods. Trying to polyfill and redefine them will only make things worse and you should stop yourself before starting such madness.

Object.defineProperty


The method defines new or modifies existing properties directly on an object, returning the object. Besides defining property accessors, the method also allows to define some neat features like writable to make the property read only, enumerable to show the property during enumeration process over object's properties and others like defining default values and ability to delete the property. You can have a look a the whole list here. Let's have a look at how it's used. I'll be showing a simple scenario without the extra features.

(function () {
   'use strict';
   function Room() {
      var temperature = null;

      Object.defineProperty(this, "temperature", {
         get: function() {
            console.log("get!"); 
            return temperature; 
        },
        set: function(value) { 
            console.log("set!"); 
            temperature = value; 
        }
    });
  }
})();
Here we declared the temperature property along with it's accessors. Take a look at line 4 - it's important to define the property before calling Object.defineProperty, otherwise you'll get an error. Once we've defined it, let's try to use it - actually you won't notice any difference:
var r = new Room();   
r.temperature = 1;
console.log(r.temperature);
The output will be:
set!
get!
1 
You'll encounter into problems once run on IE8 (of course :). Even though it supports it, there is some bad blood between the two - it could only be used on DOM objects. There is a workaround though - Object.defineProperties. It offers the same functionality as Object.defineProperty, however it is not supported by IE8 at all. How does it solve our problems? We can define it's polyfill and get full featured support in all browsers.

get and set


As of ECMAScript 5, a new syntax was introduced to help with the mess and make things cleaner - get and set. Let's see our previous example using the new syntax:
function Room() {}

Room.prototype = {
   get temperature() {
      console.log("get!");
      return this._temperature;
  },
  set temperature(temp) {
      console.log("set!");
      this._temperature = temp;
  }

};
The code of course produces the same results. You may not notice, but pay attention to the access to private member _temperature in lines 6 and 10. The attribute is created implicitly for us by runtime engine.

The problem with last solution is that it cannot be mimicked on not supporting browsers. Instead you'll get a syntax error. This is something you should take with yourself if you're willing to take such risks.

The old way


If you really need the IE support and don't like the idea of Object.defineProperties, you can always go back to creating your accessors manually:
function Room() {
   this._temperature = null;
}

Room.prototype = {
   getTemperature: function() {
      console.log("get!");
      return this._temperature;
  },
  setTemperature: function(temp) {
      console.log("set!");
      this._temperature = temp;
  }
};
And surely change your usage habits:
var r = new Room();   
r.setTemperature(1);
console.log(r.getTemperature());
Hope the article sheds some light onto the dark bog of JavaScript accessors. Be glad to here some comments.