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?

  1. Validation: Check if a value is valid before saving it.
  2. Computed Properties: Derive a value from other properties on the fly.
  3. 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.

JavaScript
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 function keyword (sayHi() {} instead of sayHi: 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
};

Categorized in:

Javascript,