DefineProperty: Data Properties and Accessor Properties
Object.defineProperty lets you go beyond key/value pairs — controlling whether a property is writable, enumerable, or configurable, and swapping in custom getters and setters via 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:
objThe object you want to define a property forpropertyNameA string, the property namepropertyDescriptorAn 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 theObject.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.
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
valueattribute 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 usingdefineProperty 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!
ES5
EcmaScript5 provides an alternative to theObject.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
functionkeyword; instead there aregetandsetkeywords
Again, invoking the setter will create a data property on the object.
__defineGetter__ and __defineSetter
This gets rather hairy... this section will come soonUsing Closures to Create Private Variables
Two of the problems that we've seen so far when creating objects withdefineProperty 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 undefinedname. 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;
}
}
}