A closer look at JavaScript Object properties

Reading time

~ 5 min read
Tags

javascript
Author

Mustapha Aouas
A closer look at JavaScript Object properties

In this post we’ll take a dive to see how objects are created then we'll talk about some interesting properties of object’s properties, but first let's start by taking a look at how we usually create an objects using object literals:

const car = { numberplate: '1234' };

We have created an object containing a property numberplate with a value of '1234'. Behind the scene javascript is using Object.create method to create this object. This is how it looks like:

const car = Object.create(
  Object.prototype,
  {
    numberplate: {
      writable: true,
      enumerable: true,
      configurable: true,
      value: '1234',
    }
  },
);

The two snippets of code above are absolutely equivalent and you can see why we use object literals, but let's take a moment to understand what is happening in the second snippet. As a first argument the Object.create takes the object which should be the prototype of the newly-created object, since we don't have/want any prototypal inheritance, we specify that it should take the default object prototype. More interestingly, the second argument specifies the property descriptors to be added to the newly-created object, with the corresponding property names.

Note that you can also use Object.defineProperties or Object.defineProperty to specify property descriptors for any given object. More on it here and here.

Let's see what each property descriptor is responsible for.

Property descriptors

Writable

The writable property descriptor defines whether the property (in this case numberplate) value can be changed from its initial value.

'use strict'

const car = { numberplate: '1234' };

Object.defineProperty(
  car,
  'numberplate',
  { writable: false }
);

car.numberplate = '0000'; // -> Uncaught TypeError

Note that if you don't use strict mode (with 'use strict') in the exemple above, the interpreter will not throw an error and the value will not be changed.

There is a caveat to be aware of. The writable property descriptor stops the pointer of the property from moving. that means if the property points toward an object, the members of that object can still be changed, for exemple:

'use strict'

const plane = {
  numberplate: { value: '1234' },
};

Object.defineProperty(
  plane,
  'numberplate',
  { writable: false }
);

plane.numberplate.value = '0000';

plane.numberplate.value // -> '0000'

plane.numberplate = {}; // -> Uncaught TypeError

Enumerable

By default object properties are enumerable, so we can enumerate over them with for...in loops and we can get them as an array with Object.keys.

Note that the difference between these two ways of getting the enumerable properties is that Object.keys returns only an array with the own properties of the object, while the for...in loop returns also the keys found in the prototype chain.

const car = {
  numberplate: '1234',
  brand: 'Koenigsegg',
};

Object.defineProperty(
  car,
  'numberplate',
  { enumerable: false }
);

Object.keys(car); // -> [brand]

Setting enumerable to false will also affect the JSON serialization of the property as it won't be serialized. That can be useful in some cases.

Configurable

The configurable descriptor of a property prevents the descriptors (of that given property ex: platenumber) from being changed. Also it prevents the property from being deleted from the object. Let's see an exemple:

'use strict'

const car = {
  numberplate: '1234',
};

Object.defineProperty(
  car,
  'numberplate',
  { configurable: false }
);

delete car.numberplate; // -> Uncaught TypeError

Object.defineProperty(
  car,
  'numberplate',
  { enumerable: false }
); // -> Uncaught TypeError

Object.defineProperty(
  car,
  'numberplate',
  { configurable: true }
); // -> Uncaught TypeError

Once you set the configurable descriptor of a property to false, you can not switch it back to true later.

One caveat to be aware of is that even if you set configurable to false, you still can change the writable descriptor.

Value

Finally the value descriptor is here to set or change the value of the property.

'use strict'

const car = {
  numberplate: '1234',
};

Object.defineProperty(
  car,
  'numberplate',
  { value: '0000' }
);

car.numberplate; // -> '0000'

Setter and getter

Getters

Another useful thing you could also do with Object.create (or Object.defineProperty or Object.defineProperties) is implementing setters and getters. Let's see how we can do it.

const point = { x: 0, y: 0 };

Object.defineProperty(
  point,
  'position',
  {
    get: function() {
      return [this.x, this.y];
    }
  }
);

point.position; // -> [0, 0]

To create a getter, you set the get attribute to a function, this function is our getter.

Setters

const point = { x: 0, y: 0 };

Object.defineProperty(
  point,
  'position',
  {
    set: function(pointArray) {
      [this.x, this.y] = pointArray;
    }
  }
);

point.position = [4, 2];

point.x; // -> 4
point.y; // -> 2

Like we did for the getter, to implement a setter we set the set attribute to a function that takes an argument, the argument is the value you want to set.

Note that when you set a getter or setter for a property, it can't have a writable or a value property descriptor. See below:

Object.getOwnPropertyDescriptor(
  point,
  'position'
); // -> { enumerable: false,
   //      configurable: false,
   //      get: ƒ, set: ƒ }

Notice that when you set a getter or a setter, the enumerable and configurable descriptors are automatically set to false. So you may want to set the getters and the setters in the same expression, or manually set configurable to true while implementing them.

Wrapping up

That's it for this post. I hope you liked it. If you did, please share it with your friends and colleagues. Also you can follow me on twitter at @theAngularGuy as it would greatly help me.

Next week we will talk about ES6 classes and how they compare to constructor functions, so make sure to stay around. Have a nice day and see you soon.