DefineProperty: Data Properties and Accessor Properties

Most of the JavaScript I write involves objects with properties known as data properties. There is an entirely other type of property known as accessor properties, and this post will attempt to illuminate the difference between the two and consider their practical applications.

Creating Data Properties

Consider the two following common ways of creating an object with a single property:

var person1 = {
  name: "Tuan"
};

var person2 = {};
person2.name = "Tuan";

Another way of defining a property on an object is via the Object.defineProperty method:

var person3 = {};
Object.defineProperty(person3, 'name', {
  value: 'Tuan'
});

Object.defineProperty() takes in three arguments:

  • obj The object you want to define a property for
  • propertyName A string, the property name
  • propertyDescriptor An object known as the property descriptor

The Property Descriptor

Before we dive into writing our own property descriptors, it is helpful to inspect the property descriptors with the Object.getOwnPropertyDescriptor method, which takes the following form:

Object.getOwnPropertyDescriptor(person, 'name')

You’ll notice that the property descriptor for person3 is quite different from the descriptors for person1 and person2.

JS Bin

That’s because the property descriptor consists of 4 attributes, not just value. When we use defineProperty, we pass an object known as the property descriptor that looks like this:

{
  configurable: true,
  enumerable: true,
  value: 'Tuan',
  writable: true
}

The default values for configurable, enumerable, and writable are false, and the default for value is undefined. The way person3 was set up, by only providing a value, the name property is:

  • Not configurable – Attributes in the property descriptor cannot be updated
  • Not enumerable – For…in loops will skip over the property
  • Not writable – The value attribute cannot be updated

However, the following will create a person similar to the one we created with object literal notation:

var person4 = {};
Object.defineProperty(person4, 'name', {
  configurable: true,
  enumerable: true,
  value: 'Tuan',
  writable: true
});

Data Properties and Accessor Properties

The previous example serves to underline a broader discussion about properties. Object literal notation creates data properties, but by using defineProperty we can define accessor properties:

var person = {};
var person = Object.defineProperty(person, 'name', {
  configurable: true,
  enumerable: true,
  get: function() {
    return this._name;
  },
  set: function(value) {
    this._name = value + "!!";
  }
});

Accessor properties differ form data properties in that they do not have value or writable attributes. Instead, they have get and set attributes, which allows the developer to write custom inspectors and mutators. With accessor properties we can modify our getters and setters perform validation when a value changes, execute a handler, or fire an event.

When [[Set]] is invoked on the name property, _name will be created. Once created, you can access it via person._name, so it’s not truly a private variable. Furthermore, it is enumerable — for-in loops will detect two properties: the name data property as well as _name accessor property!

JS Bin

ES5

EcmaScript5 provides an alternative to the Object.defineProperty method. We can define accessor properties with something similar to object literal notation:

var person = {
  get name() {
    return this._name;
  },
  set name(value){
    this._name = value;
  }
};

Note that get/set syntax here differs from object literal notation in that:

  • There are no colons to denote key/value pairs
  • There is no function keyword; instead there are get and set keywords

Again, invoking the setter will create a data property on the object.

__defineGetter__ and __defineSetter

This gets rather hairy… this section will come soon

Using Closures to Create Private Variables

Two of the problems that we’ve seen so far when creating objects with defineProperty or object literals with get/set is that 1) variables aren’t truly private, and 2) there are duplicate properties.

Consider making a constructor that returns an object literal that defines getters and setters:

function Person() {
  var _name;

  return {
    get name() {
      return _name;
    },
    set name(value) {
      _name = value;
    }
  }
}

When you invoke the constructor, a closure will maintain a reference to the internal variable _name. This will eliminate the duplicate data property on the object instance.

Object Initialization

In the previous example, our object always initializes with an undefined name. Consider the following, which initializes the person with a name if provided:

function Person(initName) {
  var _name = initName === undefined ? undefined : initName;

  return {
    get name() {
      return _name;
    },
    set name(value) {
      _name = value;
    }
  }
}

JS Bin

Additional Resources

http://ejohn.org/blog/javascript-getters-and-setters/
http://jsbin.com/lomos/2/edit