Deep Dive into JavaScript Objects: Property Descriptors, Object.create(), and Iteration

Deep Dive into JavaScript Objects: Property Descriptors, Object.create(), and Iteration

In this blog, we’ll dive deep into JavaScript objects, exploring various techniques and methods for managing properties, creating objects, and efficiently iterating over them.

Understanding Property Descriptors

Let's start with an example you're likely familiar with: Math.PI. We know that Math.PI holds the value 3.141592653589793, and you can’t change it, even if you try:

console.log(Math.PI);
Math.PI = 5;
console.log(Math.PI); // still 3.141592653589793

Why can't we change the value of Math.PI? To understand this, we can inspect the property descriptor using Object.getOwnPropertyDescriptor():

const descriptor = Object.getOwnPropertyDescriptor(Math, "PI");
console.log(descriptor);
//outputs:
{
  "value": 3.141592653589793,
  "writable": false,
  "enumerable": false,
  "configurable": false
}

We can see that writable, enumerable, and configurable are all set to false. This means the value is non-writable (cannot be changed), non-enumerable (won’t show up in loops), and non-configurable (cannot be deleted or redefined).

Declaring Objects Using Object.create()

Next, let's see a different way of creating objects using Object.create(). It allows us to create a new object that inherits from an existing one:

const obj = {
    name: "harsh", 
    age: 21
}

const mynewObj = Object.create(obj);
console.log(mynewObj); // outputs: {}

Reason for {} : When you use Object.create(obj), the new object (mynewObj) doesn't have its own properties, but it inherits properties from obj via its prototype. This means that mynewObj can access the properties of obj (like name and age), but these properties don't exist directly on mynewObj itself—they exist in its prototype.

console.log(mynewObj.name); // "harsh"
console.log(mynewObj.age);  // 21

Modifying Object Properties with Object.defineProperty()

You can modify or define properties in an object using Object.defineProperty(). This allows you to control attributes like writable, enumerable, and configurable.

const chai = {
    name: "gingerChai",
    price: 250,
    isAvailable: true
};
console.log(chai); 
//outputs: {name: 'gingerChai', price: 250, isAvailable: true}

Now does it have descriptor properties? and if yes then how can I set its descriptor properties, So first look that those properties are available to us or not.

console.log(Object.getOwnPropertyDescriptor(chai));
//undefined

Reason for this undefined: We are using Property Descriptor and we have only passed object name but we haven’t passed the property name, let’s pass that too.

console.log(Object.getOwnPropertyDescriptor(chai, "name"));
//outputs:
{
    value: 'gingerChai',
    writeable: true,
    enumerable: true,
    configurable: true
}

Now this is our code and we have written this, so javascript gives us some control hat we can change it’s properties, but this control is limited only to some extent. so we can’t do something wrong by changing any property.

It takes 3 parameters:

  1. Object name

  2. The property that we want to disrupt

  3. define the properties inside the object

const chai = {
    name: "gingerChai",
    price: 250,
    isAvailable: true
};

Object.defineProperty(chai, "name", {
    writable: false,
    enumerable: false
});

console.log(Object.getOwnPropertyDescriptor(chai, "name"));
//outputs:
{
  "value": "gingerChai",
  "writable": false,
  "enumerable": false,
  "configurable": true
}

Now, if you try to change the name property or iterate over it, you’ll notice the following:

  • The name property is non-enumerable, meaning it won’t show up in loops.

  • The name property is non-writable, meaning you can't change its value.

Iterating Over Object Properties

If we want to loop through the object keys and values, we can use for...of combined with Object.entries(), first let try to loop over it without Object.entries():

for (let [key, value] of chai) {  
    console.log(`${key}: ${value}`);
}
//outputs: throws error (chai is not iterable)

Object is iterable or not depends on different situation, now to iterate over this object we can use Object.entries():

for (let [key, value] of Object.entries(chai)) {  
    console.log(`${key}: ${value}`);
}
//outputs:
price: 250
isAvailable: true

Since we set enumerable: false for the name property, it’s no longer included in the iteration.

Adding Functions to Objects

You can also add methods (functions) to objects, like this:

const chai = {
    name: "gingerChai",
    price: 250,
    isAvailable: true,

    orderChai: function() {
        console.log("Chai is not ready");
    }
};

for (let [key, value] of Object.entries(chai)) {  
    console.log(`${key}: ${value}`);
}
price: 250
isAvailable: true
orderChai: function(){
        console.log("chai is not ready");
    }

we can see we are getting the function directly and sometimes we want to skip some iterations in object or array, so we can add a if condition to skip this function iteration

for (let [key, value] of Object.entries(chai)) { 
    if (typeof value !== "function") {
        console.log(`${key}: ${value}`);
    }
}
//outputs: 
price: 250
isAvailable: true

Wrapping Up

In this blog, we explored several key concepts related to JavaScript objects, including:

  • Viewing and manipulating property descriptors.

  • Creating objects with Object.create() and understanding prototype inheritance.

  • Modifying object properties with Object.defineProperty().

  • Iterating over objects and filtering out certain properties.

By mastering these techniques, you can work more effectively with JavaScript objects and have better control over how properties behave.