JavaScript lacks explicit access modifiers, so variable “privacy” is hard to come by :-) . Instead we have to come up with clever workarounds to encapsulate objects to simulate the type of private scope we are used to from other programming languages. In this post I will demonstrate how to use Object.defineProperty to lock down JavaScript properties.

One common approach for tagging variables as “private” is through variable naming conventions like _ or $$ prefixes. However, the problem with conventions is that there is always the chance that something will be overwritten inadvertently.

Object.defineProperty changes how we define properties by giving us granular control over the behavior of the property. When using Object.defineProperty, properties are defined using a meta object that describes some behavioral attributes of the property. Basically, we have control over writability, initial value, enumerability and configurability. The meta object contains properties for controlling this behavior.

Sample meta object

{writable:true,enumerable:true,configurable:true,value:100}

Value

Value is the simplest one to understand – basically it's how we give the property an initial value.

Writable

Perhaps the most useful feature is the ability to define constants that can't be reset from code after the initial declaration. In the example below I will demonstrate how to create a constant by setting the writable attribute to false. Any attempt to write to a read-only property will not update the property and fail silently.

it('should create constant',function(){ var person = {hairColor:'brown', name:'Joe'}; Object.defineProperty(person,'age',{ value:20, writable:false }); expect(person.age).toBe(20); person.age = 35; expect(person.age).toBe(20); }); it('should create writable property',function(){ var person = {hairColor:'brown', name:'Joe'}; Object.defineProperty(person,'age',{ value:20, writable:true }); expect(person.age).toBe(20); person.age = 35; expect(person.age).toBe(35); });

There is however a caveat here. Even though the property can't be overwritten – it still isn't immutable.

As you can see from the code below we are not able to reassign the property to a new object, but we are still able to change sub properties on the property.

it('should still me mutable', function(){ var person = {hairColor:'brown', name:'Joe'}; Object.defineProperty(person,'description',{ value:{hairColor:'brown', name:'Joe',age:20}, writable:false }); person.description = {hairColor:'brown', name:'Joe',age:50}; expect(person.description.age).toBe(20); person.description.age = 50; expect(person.description.age).toBe(50); });

Enumerable

Enumerability lets us specify if we want the property to be visible to a for-in loop. As you can see below setting this attribute to false hides it from the enumeration.

it('should show up in enumerations of properties', function(){ var person = {hairColor:'brown', name:'Joe'}; Object.defineProperty(person,'age',{ value:20, enumerable:true }); var props = []; for(var prop in person){ props.push(prop); } expect(props.length).toBe(3); expect(props.indexOf('hairColor') !== -1).toBe(true); expect(props.indexOf('name') !== -1).toBe(true); expect(props.indexOf('age') !== -1).toBe(true); }); it('should not show up in enumerations of properties', function(){ var person = {hairColor:'brown', name:'Joe'}; Object.defineProperty(person,'age',{ value:20, enumerable:false }); var props = []; for(var prop in person){ props.push(prop); } expect(props.length).toBe(2); expect(props.indexOf('hairColor') !== -1).toBe(true); expect(props.indexOf('name') !== -1).toBe(true); expect(props.indexOf('age') !== -1).toBe(false); });

Configurable

Configurable allows us to specify if we want to allow the property to be reconfigured by calling Object.defineProperty again – with new meta values.

Here's an example of how to use 'configurable'

it('should allow reconfigurations', function(){ var person = {hairColor:'brown', name:'Joe'}; Object.defineProperty(person,'age',{ value:20, writable:false, configurable:true }); person.age = 50; expect(person.age).toBe(20); Object.defineProperty(person,'age',{ value:30, writable:true }); expect(person.age).toBe(30); person.age = 50; expect(person.age).toBe(50); });