Advance JavaScript (DOM, BOM, Web API) / Object Oriented Programming

Property Descriptors

When you create an object property in JavaScript, it comes with hidden metadata.

These metadata are called Property Descriptors.

They control how a property behaves:

  • Can you change its value?

  • Can you delete it?

  • Is it visible during looping?

  • Does it have getter/setter?


🔥 Types of Property Descriptors

  1. Data Property Descriptor → describes a normal property storing a value

  2. Accessor Property Descriptor → describes getter/setter based property


🧩 1. Data Property Descriptor Fields

Every normal property has these three descriptor fields:

Field

Default

Meaning

value

the actual value

Stores the value

writable

true

Can the value be changed?

enumerable

true

Will it show in loops like for...in?

configurable

true

Can we delete or change descriptors?

✔️ Example

const user = {
  name: "Bhaskar"
};

console.log(Object.getOwnPropertyDescriptor(user, "name"));
{
  value: "Bhaskar",
  writable: true,
  enumerable: true,
  configurable: true
}

🧩 2. Accessor Property Descriptor Fields

Used when a property has get or set:

Field

Meaning

get

function that returns value

set

function that sets value

enumerable

loop

configurable

delete or change

❗ Note: Accessor descriptors do NOT use value or writable.


✔️ Example of Accessor Property

const user = {
  firstName: "Bhaskar",
  lastName: "Das",

  get fullName() {
    return this.firstName + " " + this.lastName;
  },

  set fullName(value) {
    const [f, l] = value.split(" ");
    this.firstName = f;
    this.lastName = l;
  }
};

console.log(user.fullName);  // Bhaskar Das

user.fullName = "Amit Singh";

console.log(user.firstName); // Amit
console.log(user.lastName);  // Singh
console.log(Object.getOwnPropertyDescriptor(user, "fullName"));
{
	enumerable: true, 
	configurable: true, 
	get: ƒ fullName()
				length: 0
				name: "get fullName"
	set: ƒ fullName(value)
				length: 1
				name: "set fullName"
}

🛠️ Object.defineProperty()

Used to manually control descriptors.

Example: make a property read-only

const user = {};
Object.defineProperty(user, "name", {
  value: "Bhaskar",
  writable: false,      // ❌ cannot change
  enumerable: true,
  configurable: true
});

user.name = "Das";  // ignored
console.log(user.name); // Bhaskar

Example: Hide functions/methods from loops

const user = {
  name: "Bhaskar",
  age: 25
};

// Add method manually and hide it from loops
Object.defineProperty(user, "sayHi", {
  value: function () {
    console.log("Hi!");
  },
  enumerable: false // 🔥 method will NOT appear in loops
});

console.log(Object.keys(user));

for (let key in user) {
  console.log(" ->", key);
}
[ 'name', 'age' ]
-> name 
-> age

// When enumerable: true
['name', 'age', 'sayHi']
-> name
-> age
-> sayHi

⚠️ Important Notes

✔️ 1. If you use defineProperty(), unspecified descriptors default to:

  • writable: false

  • enumerable: false

  • configurable: false

✔️ 2. Accessor and Data descriptors cannot be mixed:

// ❌ invalid
Object.defineProperty({}, "x", {
  value: 10,
  get() {
    return 20;
  }
});

🧠 Why Property Descriptors Are Useful?

  • To create read-only properties

  • To hide properties from loops (enumerable: false)

  • To lock API objects (configurable: false)

  • To create libraries/frameworks (React, Vue, etc. use them internally)

  • To control getters/setters