Episode 1 — Fundamentals / 1.23 — Objects
1.23.b — Key-Value Pairs
In one sentence: Every object property is a key-value pair where the key is always a string (or Symbol) and the value can be any type — including functions, which turn properties into methods.
Navigation: ← 1.23 Overview · 1.23.c — Accessing Properties →
1. Properties — keys and values
An object is essentially a bag of properties. Each property has:
- A key (also called a property name) — always coerced to a string unless it is a Symbol.
- A value — can be any JavaScript type.
const product = {
name: "Wireless Mouse", // key: "name", value: string
price: 29.99, // key: "price", value: number
inStock: true, // key: "inStock", value: boolean
tags: ["electronics"], // key: "tags", value: array
manufacturer: null, // key: "manufacturer", value: null
};
Keys are always strings (internally)
Even if you use a number as a key, JavaScript converts it to a string:
const obj = { 1: "one", 2: "two" };
console.log(obj["1"]); // "one"
console.log(obj[1]); // "one" — 1 is coerced to "1"
// Proof:
console.log(Object.keys(obj)); // ["1", "2"] — strings!
2. Property shorthand
When a variable name matches the key you want, use shorthand notation to avoid repetition:
const name = "Alice";
const age = 25;
const role = "admin";
// Without shorthand — repetitive
const user1 = { name: name, age: age, role: role };
// With shorthand — clean and idiomatic
const user2 = { name, age, role };
console.log(user2); // { name: "Alice", age: 25, role: "admin" }
You can mix shorthand and regular properties:
const email = "alice@example.com";
const user = {
email, // shorthand
username: "alice", // regular
isActive: true, // regular
};
When to use shorthand
| Scenario | Recommendation |
|---|---|
| Variable name matches desired key | Use shorthand — less noise |
| Variable name differs from desired key | Use regular key: value syntax |
| Building objects from function parameters | Shorthand shines — return { name, age } |
3. Computed property names
Wrap a key in square brackets [] to compute it from an expression:
const field = "email";
const user = {
name: "Alice",
[field]: "alice@example.com", // key is "email"
};
console.log(user.email); // "alice@example.com"
Dynamic keys from expressions
const prefix = "user";
const id = 42;
const obj = {
[`${prefix}_${id}`]: "Alice", // key is "user_42"
};
console.log(obj["user_42"]); // "Alice"
Common use case — building objects from data
function buildLookup(items, keyProp) {
const lookup = {};
for (const item of items) {
lookup[item[keyProp]] = item; // dynamic key assignment
}
return lookup;
}
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
const byId = buildLookup(users, "id");
console.log(byId[1]); // { id: 1, name: "Alice" }
Using computed property names inside the literal:
function createPair(key, value) {
return { [key]: value };
}
console.log(createPair("color", "blue")); // { color: "blue" }
4. String keys vs identifier keys — when quotes are needed
Most keys are written as bare identifiers (no quotes). You need quotes when the key:
- Contains spaces or special characters
- Starts with a number
- Is a reserved word (though modern JS allows most reserved words as keys)
- Contains hyphens (common in CSS-like properties or HTTP headers)
const obj = {
name: "Alice", // valid identifier — no quotes needed
"full name": "Alice J.", // space — quotes required
"content-type": "json", // hyphen — quotes required
"123start": true, // starts with number — quotes required
class: "A", // reserved word — works without quotes in modern JS
};
// Access quoted keys with bracket notation
console.log(obj["full name"]); // "Alice J."
console.log(obj["content-type"]); // "json"
| Key format | Quotes needed? | Access |
|---|---|---|
name | No | obj.name or obj["name"] |
full name | Yes | obj["full name"] only |
content-type | Yes | obj["content-type"] only |
123abc | Yes | obj["123abc"] only |
class | No (modern JS) | obj.class or obj["class"] |
5. Symbol keys
Symbols are unique, immutable identifiers. They create "hidden" properties that do not collide with string keys and do not appear in for...in or Object.keys:
const SECRET = Symbol("secret");
const ID = Symbol("id");
const user = {
name: "Alice",
[SECRET]: "classified-data",
[ID]: 42,
};
console.log(user.name); // "Alice"
console.log(user[SECRET]); // "classified-data"
console.log(user[ID]); // 42
// Symbol keys are NOT enumerated by default
console.log(Object.keys(user)); // ["name"]
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(secret), Symbol(id)]
When to use Symbol keys
- Library / framework internals — avoid collision with user-defined keys
- Well-known symbols —
Symbol.iterator,Symbol.toPrimitive, etc. - Truly private-like metadata — not visible to serialization or casual iteration
For day-to-day code, string keys are the standard. Symbols are an advanced tool.
6. Values can be any type
Object values have no type restrictions:
const demo = {
// Primitives
str: "hello",
num: 42,
bool: true,
nothing: null,
missing: undefined,
big: 9007199254740991n, // BigInt
sym: Symbol("demo"), // Symbol
// Reference types
arr: [1, 2, 3],
nested: { inner: "value" },
fn: function() { return "I'm a function"; },
arrow: () => "I'm an arrow function",
date: new Date(),
regex: /pattern/gi,
};
console.log(demo.str); // "hello"
console.log(demo.arr[0]); // 1
console.log(demo.nested.inner); // "value"
console.log(demo.fn()); // "I'm a function"
console.log(demo.arrow()); // "I'm an arrow function"
7. Methods — functions as property values
When a property's value is a function, we call it a method:
const calculator = {
result: 0,
add: function(n) {
this.result += n;
return this; // enables chaining
},
subtract: function(n) {
this.result -= n;
return this;
},
reset: function() {
this.result = 0;
return this;
},
getResult: function() {
return this.result;
},
};
calculator.add(10).add(5).subtract(3);
console.log(calculator.getResult()); // 12
8. Method shorthand vs function expression
ES6 introduced a shorthand for methods — omit : function:
// Function expression (ES5 style)
const userES5 = {
name: "Alice",
greet: function() {
return "Hello, I'm " + this.name;
},
};
// Method shorthand (ES6+ — preferred)
const userES6 = {
name: "Alice",
greet() {
return `Hello, I'm ${this.name}`;
},
};
console.log(userES5.greet()); // "Hello, I'm Alice"
console.log(userES6.greet()); // "Hello, I'm Alice"
Comparison table
| Style | Syntax | this binding | Can be constructor? | Recommended? |
|---|---|---|---|---|
| Function expression | greet: function() {} | Dynamic (caller) | Yes (new obj.greet()) | Legacy |
| Method shorthand | greet() {} | Dynamic (caller) | No | Yes |
| Arrow function | greet: () => {} | Lexical (enclosing scope) | No | Not for methods |
Why NOT arrow functions for methods
const user = {
name: "Alice",
greet: () => {
// `this` is NOT the object — it's the enclosing scope (e.g. `window` or `undefined`)
return `Hello, I'm ${this.name}`;
},
};
console.log(user.greet()); // "Hello, I'm undefined" — WRONG!
Arrow functions do not have their own this — they inherit it from the surrounding lexical scope. For object methods that need this, always use method shorthand or function expressions.
9. this inside methods — the basics
Inside a method, this refers to the object the method was called on:
const dog = {
name: "Rex",
breed: "Labrador",
describe() {
return `${this.name} is a ${this.breed}`;
},
};
console.log(dog.describe()); // "Rex is a Labrador"
The key rule
this is determined by how the function is called, not where it is defined:
const dog = {
name: "Rex",
describe() {
return `${this.name}`;
},
};
// Called as a method — this === dog
console.log(dog.describe()); // "Rex"
// Extracted and called standalone — this is undefined (strict mode) or window
const fn = dog.describe;
// console.log(fn()); // TypeError or "undefined" — `this` is lost!
Deep dive on
thisis covered in functions and advanced topics. For now, remember: call a method on its object to keepthiscorrect.
10. Real examples
Building a user object
function createUser(name, email, role = "viewer") {
return {
name,
email,
role,
createdAt: new Date().toISOString(),
isActive: true,
deactivate() {
this.isActive = false;
},
promote(newRole) {
this.role = newRole;
},
summary() {
return `${this.name} (${this.email}) — ${this.role}`;
},
};
}
const alice = createUser("Alice", "alice@example.com", "admin");
console.log(alice.summary()); // "Alice (alice@example.com) — admin"
alice.deactivate();
console.log(alice.isActive); // false
Configuration object with methods
const appConfig = {
settings: {
theme: "light",
language: "en",
notifications: true,
},
get(key) {
return this.settings[key];
},
set(key, value) {
this.settings[key] = value;
},
reset() {
this.settings = {
theme: "light",
language: "en",
notifications: true,
};
},
toJSON() {
return JSON.stringify(this.settings, null, 2);
},
};
appConfig.set("theme", "dark");
console.log(appConfig.get("theme")); // "dark"
console.log(appConfig.toJSON());
// {
// "theme": "dark",
// "language": "en",
// "notifications": true
// }
Key takeaways
- Every property has a key (string or Symbol) and a value (any type).
- Property shorthand
{ name }avoids repetition when the variable name matches the key. - Computed property names
{ [expr]: value }let you build keys dynamically. - Keys that contain spaces, hyphens, or start with digits need quotes and bracket access.
- Symbol keys create non-enumerable, collision-free properties — mostly for libraries and internals.
- Methods are properties whose values are functions — use method shorthand
greet() {}in ES6+. - Arrow functions should not be used as methods because they do not bind their own
this. thisinside a method refers to the calling object — but only when called with dot notation on that object.
Explain-It Challenge
Explain without notes:
- What is property shorthand and when does it apply?
- Why would you use computed property names
[expr]? Give a real example. - Why should you avoid using arrow functions as object methods? What happens to
this?
Navigation: ← 1.23 Overview · 1.23.c — Accessing Properties →