-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
40: Property Descriptors and Getters/Setters
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...inloops orObject.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 inObject.keys()). - Create a property called
volume. Use a setter to ensure that any value assigned tovolumeis 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
statusthat returns the string"System Active"ifvolumeis greater than 0, and"System Muted"ifvolumeis 0.
Test your implementation by attempting to overwrite the apiKey and by assigning values outside the 0-100 range to volume.
There are no comments for now.