In JavaScript, properties are the essential building blocks of objects. They are essentially key-value pairs that define the characteristics and behaviors of an object. While they may seem simple, JavaScript properties are governed by deep underlying mechanics involving descriptors, enumerability, ownership, and accessors.
1. Property Types: Data vs. Accessor
Properties are fundamentally categorized into two types. Understanding the distinction is crucial for building robust APIs and encapsulation patterns.
Data Properties
Data properties are the “standard” properties we use most often. They associate a key with a specific value.
- Definition: They contain a single location for a data value.
- Attributes: They use value and writable (alongside the shared enumerable and configurable).
- Behavior: When you get the property, you get the stored value. When you set it, you overwrite that value.
JavaScript
const user = {
role: 'admin' // This is a Data Property
};
Accessor Properties
Accessor properties do not contain a data value. Instead, they define a getter function to read the property and a setter function to write to it.
- Definition: They “intercept” the getting and setting process.
- Attributes: They use get and set instead of value and writable.
- Behavior: When you access the property, the get function executes. When you assign a value, the set function executes.
Why use Accessors?
- Validation: Check if a value is valid before saving it.
- Computed Properties: Derive a value from other properties on the fly.
- Encapsulation: Hide internal implementation details.
JavaScript
const account = {
_balance: 1000, // Internal "private" data
get balance() {
console.log("Checking balance...");
return `$${this._balance}`;
},
set balance(amount) {
if (amount < 0) throw new Error("Cannot have negative balance");
this._balance = amount;
}
};
console.log(account.balance); // "Checking balance..." -> "$1000"
account.balance = 500; // Updates _balance
2. Property Attributes (Internal Descriptors)
Every property in JavaScript is more than just a name and a value. Internally, each property is defined by a Property Descriptor—a set of attributes that tell the JavaScript engine how that property should behave. You can inspect these using Object.getOwnPropertyDescriptor(obj, prop).
| Attribute | Description | Default (via Literal) |
value |
The actual data value. | undefined |
writable |
If false, the value cannot be changed. |
true |
enumerable |
If true, the property shows up in for...in and Object.keys(). |
true |
configurable |
If false, the property cannot be deleted or its attributes modified. |
true |
Fine-Grained Control
Using Object.defineProperty(), you can create “read-only” or “hidden” properties that cannot be deleted, which is essential for library authors and protecting internal application state.
const system = {};
Object.defineProperty(system, 'id', {
value: '7a2-f89',
writable: false, // Cannot change value
enumerable: false, // Won't show up in loops
configurable: false // Cannot be deleted
});
3. Property Ownership and the Prototype Chain
A critical distinction in JavaScript is whether a property belongs to the object itself or is being “borrowed” from its ancestor.
- Own Properties: Properties defined directly on the object instance.
- Inherited Properties: Properties that exist on the object’s Prototype.
When you access a property, the JavaScript engine first looks at the “Own Properties.” If it fails to find a match, it climbs the Prototype Chain until it either finds the property or reaches null.
Identifying Ownership
To safely check if an object has a property without looking up the prototype chain, use the modern Object.hasOwn() method.
JavaScript
const blueprint = { category: "Appliance" };
const toaster = Object.create(blueprint);
toaster.brand = "ToastMaster";
console.log(Object.hasOwn(toaster, "brand")); // true (Own)
console.log(Object.hasOwn(toaster, "category")); // false (Inherited)
4. Symbols as Property Keys
Symbols are unique and immutable primitives used to create “hidden” or private-like properties. They do not appear in standard for...in loops or Object.keys(), making them ideal for adding metadata to objects without interfering with their public API.
JavaScript
const internalId = Symbol('id');
const client = {
name: "Acme Corp",
[internalId]: 9988
};
console.log(Object.keys(client)); // ["name"] (internalId is hidden)
console.log(client[internalId]); // 9988
5. Property Names and Computed Keys
Property keys must be either Strings or Symbols. If you use another type (like a Number or Object), JavaScript implicitly converts it to a string.
Computed Property Names
Introduced in ES6, this syntax allows you to use a variable or an expression as a property key directly inside the object literal.
JavaScript
const prefix = "user_";
const dynamicObj = {
[`${prefix}id`]: 500
}; // Resulting key: "user_id"
6. Property Shorthands and Computed Names (ES6+)
Modern JavaScript introduced “Syntactic Sugar” to make property definition more concise:
- Property Shorthand: If the variable name matches the key, you can omit the value (
{ name }instead of{ name: name }). - Method Shorthand: You can omit the
functionkeyword (sayHi() {}instead ofsayHi: function() {}). - Computed Properties: You can use square brackets inside the literal to define keys dynamically.
JavaScript
const dynamicKey = "userId";
const user = {
[dynamicKey]: 12345, // Computed property
login() { console.log("Success"); } // Method shorthand
};
