Skip to Content
Course content

40: Property Descriptors and Getters/Setters

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely spent the last few lessons treating JavaScript objects like simple dictionaries. You put a key in, you get a value out. Because of this, most developers walk around with a specific misconception: they think that when they write user.name = 'Alice', they are simply updating a piece of data in a memory slot.

It feels intuitive. It feels direct. But in reality, that assignment is actually a request that JavaScript evaluates. If the property is configured a certain way, your assignment might be silently ignored, it might throw an error, or it might trigger a complex function you didn't even know existed.

The Lie: Assignment is Just Data Storage

const user = {
  id: 101,
  username: 'dev_guru'
};

// I'm just changing a value, right?
user.id = 202; 
console.log(user.id); // 101 (Wait, what?)

If you ran the code above in a strict environment or with a specifically defined object, you'd realize that user.id didn't change. Why? Because I didn't just create a standard property; I used a Property Descriptor. In the example above, I've conceptually set the writable attribute to false. You tried to change the data, but the engine looked at the descriptor and said, "No, this specific property is read-only."

The Truth: Properties Have Hidden Metadata

Every property in JavaScript isn't just a value; it's a set of attributes. When you create an object using the literal {} syntax, JS gives you some sensible defaults, but you can override them using Object.defineProperty(). I use this all the time when I'm building libraries and I don't want the end-user to accidentally break my internal state.

There are three main flags you need to care about:

  • writable: If false, the value cannot be changed. It becomes a constant.
  • enumerable: If false, the property won't show up in for...in loops or Object.keys(). It's effectively "hidden" from iteration.
  • configurable: This is the "meta" flag. If false, you can't delete the property and you can't change any of these other flags later. It locks the property's definition.
const appConfig = {};

Object.defineProperty(appConfig, 'version', {
  value: '1.0.0',
  writable: false,      // Can't change the version
  enumerable: true,     // Can see it in logs
  configurable: false   // Can't even redefine this descriptor
});

appConfig.version = '2.0.0'; // Silently fails (or throws in strict mode)
console.log(appConfig.version); // '1.0.0'

Turning Properties into Logic with Getters and Setters

Now, here is where things get powerful. Sometimes you don't want a static value at all. You want a property that acts like a value but executes code when accessed. This is where getters and setters come in. I think of them as "virtual properties."

Imagine you have a User object. You have firstName and lastName, but you constantly need the fullName. Instead of calling a function like getFullName(), you can make fullName look like a property.

const user = {
  firstName: 'Sarah',
  lastName: 'Connor',
  
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  
  set fullName(value) {
    const parts = value.split(' ');
    this.firstName = parts[0];
    this.lastName = parts[1];
  }
};

console.log(user.fullName); // 'Sarah Connor' (Getter triggered!)
user.fullName = 'Kyle Reese'; // (Setter triggered!)
console.log(user.firstName); // 'Kyle'

Notice that fullName doesn't actually exist as a stored string in the object. It's a computed value. This is incredibly useful for validation. If you have a set age(val), you can throw an error if val is negative before it ever touches your data. It's a way of putting a guard rail around your object's state.




📋 Practical Task

Building a Protected SystemSettings Store

Your task is to create a systemSettings object that mimics a protected configuration file. Follow these strict requirements:

  • Create a property called apiKey. This property must be read-only (cannot be changed) and non-enumerable (should not appear in Object.keys()).
  • Create a property called volume. Use a setter to ensure that any value assigned to volume is clamped between 0 and 100. If a user tries to set it to 150, it should be stored as 100. If they set it to -20, it should be stored as 0.
  • Create a getter called status that returns the string "System Active" if volume is greater than 0, and "System Muted" if volume is 0.

Test your implementation by attempting to overwrite the apiKey and by assigning values outside the 0-100 range to volume.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.