Object Property Descriptors in JavaScript

If you’ve ever used JavaScript there is a 99.9999999% change that you’ve used objects. That’s obvious. What is not that obvious is that there is more to an object and its properties than what meets the eye. ECMAScript 2015 introduced the concept of property descriptors. A property descriptor is essentially a set of property metadata that describes how the property should behave. You can access a property’s descriptor using the Object.getOwnPropertyDescriptor function. You can define a new property along with its descriptor or modify an already existing property’s descriptor with Object.defineProperty. Let’s see descriptors in action:

var obj = {
a: 1,
b: 2,
c: 3
};

console.log(Object.getOwnPropertyDescriptor(obj, “a”));

This prints the property’s descriptor in our console window:

blog1

As you can see a property’s descriptor is itself an object with 4 properties. The obvious one is the value which is, like the name implies, the value set to the property (in this example obj.a).

The other three are the most important and the ones in need of further discussion. All you need to know is summarized in the following lists:

(defineProperty can be used to define a new property along with its metadata or modify an existing one provided that its descriptor’s configurable property is set to true)

Writable

  •  If set to true the value can be changed
  •  If set to false the value cannot be changed
    •  In non-strict mode any attempt to change the property’s value will fail silently
    •  In strict-mode any attempt to change the property’s value will trigger a TypeError

Let’s see some examples

var obj = {};

//We define a new property called 'foo' in obj with 'writable' explicitly set to true
Object.defineProperty(obj, "foo", {
	value: 100,
    writable: true
});

console.log(obj.foo);	//100;
//foo's value can be changed
obj.foo =  "foo";
console.log(obj.foo);	//'foo'

When we explicitly set ‘writable’ to false, the property’s value cannot be changed after the initial assignment.
The first example runs in non-strict mode while the second one is in strict mode and therefore the assignment of property ‘foo’ results in an error

Example #1: Non Strict Mode

var obj = {};

//We define a new property called 'foo' in obj with 'writable' explicitly set to false
Object.defineProperty(obj, "foo", {
    value: 100,
    writable: false
});

console.log(obj.foo);	//100;
//Assignment fails silently
obj.foo =  "foo";
console.log(obj.foo);	//100

Example #2: Strict Mode

"use strict"

var obj = {};

//We define a new property called 'foo' in obj with 'writable' explicitly set to false
Object.defineProperty(obj, "foo", {
	value: 100,
    writable: false
});

console.log(obj.foo);	//100;
//Assignment fails silently
obj.foo =  "foo";
console.log(obj.foo);	//Uncaught TypeError: Cannot assign to read only property 'foo'

Configurable

  • If set to true we can use Object.defineProperty to modify the property’s descriptor
  • If set to false we cannot use Object.defineProperty to modify the property’s descriptor
  • If set to false we cannot use’ delete’ to remove the property.
    •  In strict mode a TypeError is thrown.
    •  In non-strict mode the removal fails silently
  •  Exception: If set to false the only thing allowed to change is the writable status from true to false and not vice versa (TypeError thrown)

Example #3: Setting ‘configurable’ to false and then attempting to change ‘enumerable’ to false

var obj = {
	a: 1,
    b: 2,
    c: 3
};

Object.defineProperty(obj, "a", {
	configurable: false
});

Object.defineProperty(obj, "a", {
	enumerable: false	
});	//Uncaught TypeError: Cannot redefine property: a

Example #4: Trying to remove a non-configurable property using the ‘delete’ keyword (non-strict mode):

var obj = {
	a: 1,
    b: 2,
    c: 3
};

Object.defineProperty(obj, "a", {
	configurable: false
});

console.log(obj);	//{a: 1, b: 2, c: 3}
delete obj.c;
console.log(obj);	//{a: 1, b: 2}
delete obj.a;	//Fails silently
console.log(obj);	//{a: 1, b: 2}	(a is still here)

Example #5: Trying to remove a non-configurable property using the ‘delete’ keyword (strict mode):

"use strict"

var obj = {
	a: 1,
    b: 2,
    c: 3
};

Object.defineProperty(obj, "a", {
	configurable: false
});

console.log(obj);	//{a: 1, b: 2, c: 3}
delete obj.c;
console.log(obj);	//{a: 1, b: 2}
delete obj.a;	//Uncaught TypeError: Cannot delete property 'a'

Example #6: Setting ‘writable’ to false even when ‘configurable’ has already been set to false

Have you noticed that in Example #3 the property we tried to change after setting ‘configurable’ to true is the ‘enumerable’ property even though we haven’t talked about it yet? Well the reason why, is that there is an exception to what cannot be changed once configurable is set to false and that is when ‘writable’ is true. See the next example:

"use strict"

var obj = {
	a: 1,
    b: 2,
    c: 3
};

//We set configurable to false so theoretically we cannot change anything from here on
Object.defineProperty(obj, "b", {
	configurable: false
});

//You would expect an error but this operation completes
Object.defineProperty(obj, "b", {
	writable: false
});

obj.b = 120;	//Oops! Type error

If you run this piece of code you’re going to see that we managed to successfully make ‘b’ immutable even with ‘configurable’ set to false. And this is the one and only exception to the rule. Also note that if we try to change ‘writable’ back to true it won’t work as the exception is not bidirectional

Enumerable

  • If set to true the property is enumerable and will show up in a for .. In loop
  • If set to false the property will not show up in a for..in loop

 

Example #7: Omitting a property from the list of enumerables


function printEverything(obj){
	for(var prop in obj){
    	if(obj.hasOwnProperty(prop)){
        	console.log(prop + ": " + obj[prop]);
        }
    }
}

var obj = {
	a: 1,
    b: 2,
    c: 3
}

printEverything(obj);	//a: 1, b: 2, c: 3
Object.defineProperty(obj, "b", {
	enumerable: false
});
console.log("");
printEverything(obj);	//a: 1, c: 3

The most common use case scenario for modifying property descriptors will probably be when you want to create an immutable property. See the following example:

Example #8: Real life scenario: Immutable Property

var obj = {
	a: 1,
    b: 2
}

//We create a new property in 'obj' setting it to 18. That value cannot be changed. 'enumerable' set to true by default
Object.defineProperty(obj, "c", {
	value: 18,
    writable: false,
    configurable: false
});

console.log(obj);	//{ a: 1, b: 2, c: 18 }
obj.c = 5;
console.log(obj.c);	//18, No error, we're not in strict mode

That’s all for now. I will be coming back with another blog post discussing more stuff about property descriptors and how you can perform common descriptor operations faster with the use of methods like seal, freeze and preventExtensions. Until then.. Keep coding….