Object Literals
The object literal {} is the most common way to create an object. A property is a key-value pair; the key is always a string (or Symbol), but the value can be anything.
// Basic object literal
const user = {
name: 'Alice',
age: 28,
isAdmin: false,
address: {
city: 'London',
country: 'UK'
},
hobbies: ['reading', 'coding']
};
// Keys can be multi-word strings (use quotes)
const config = {
'max-retries': 3,
'api-url': 'https://api.example.com'
};
// Empty object
const empty = {};
console.log(typeof user); // 'object'
console.log(user.name); // 'Alice'
console.log(user.address.city); // 'London'
Dot Notation vs Bracket Notation
Both notations read and write properties. Use dot notation when the key is a valid identifier; use bracket notation for dynamic keys or keys with special characters.
const person = { name: 'Bob', age: 30 };
// Dot notation
console.log(person.name); // 'Bob'
person.email = 'bob@example.com'; // add property
// Bracket notation
console.log(person['age']); // 30
person['phone'] = '555-1234';
// Dynamic key (only bracket notation works here)
const key = 'name';
console.log(person[key]); // 'Bob' β equivalent to person.name
// Multi-word keys require bracket notation
const cfg = { 'max-retries': 5 };
console.log(cfg['max-retries']); // 5
// console.log(cfg.max-retries); // SyntaxError
// Deleting a property
delete person.phone;
console.log(person.phone); // undefined
Bob 30 Bob 5 undefined
Object Methods and the this Keyword
When a function is stored as a property of an object, it is called a method. Inside a method, this refers to the object the method was called on.
const calculator = {
value: 0,
// Method shorthand (ES6)
add(n) {
this.value += n;
return this; // enables chaining
},
subtract(n) {
this.value -= n;
return this;
},
result() {
return this.value;
}
};
const answer = calculator.add(10).add(5).subtract(3).result();
console.log(answer); // 12
// Beware: arrow functions do NOT bind their own 'this'
const counter = {
count: 0,
increment: () => {
// this is the outer scope (window/undefined in strict mode)
this.count++; // won't work as expected
},
decrement() {
this.count--; // regular method β 'this' is counter
}
};
Arrow functions inherit this from their surrounding lexical scope, not from the object. Always use regular function syntax (method() {} or method: function() {}) when you need this to refer to the object.
Object.keys, Object.values, Object.entries
const product = { id: 1, name: 'Laptop', price: 999 };
// Object.keys β array of property names
console.log(Object.keys(product)); // ['id', 'name', 'price']
// Object.values β array of property values
console.log(Object.values(product)); // [1, 'Laptop', 999]
// Object.entries β array of [key, value] pairs
console.log(Object.entries(product));
// [['id', 1], ['name', 'Laptop'], ['price', 999]]
// Iterating with for...of + destructuring
for (const [key, value] of Object.entries(product)) {
console.log(`${key}: ${value}`);
}
// Convert entries back to an object
const doubled = Object.fromEntries(
Object.entries(product).map(([k, v]) =>
typeof v === 'number' ? [k, v * 2] : [k, v]
)
);
console.log(doubled); // { id: 2, name: 'Laptop', price: 1998 }
Object.assign and Spread
const defaults = { theme: 'light', lang: 'en', debug: false };
const userPrefs = { theme: 'dark', notifications: true };
// Object.assign β copies properties into target (mutates target)
const merged1 = Object.assign({}, defaults, userPrefs);
// { theme: 'dark', lang: 'en', debug: false, notifications: true }
// Spread operator β cleaner, preferred in modern code
const merged2 = { ...defaults, ...userPrefs };
// { theme: 'dark', lang: 'en', debug: false, notifications: true }
// Shallow copy with spread
const original = { a: 1, nested: { b: 2 } };
const copy = { ...original };
copy.a = 99;
copy.nested.b = 99; // mutates ORIGINAL.nested too β it's a shallow copy!
console.log(original.nested.b); // 99 β shared reference
// Shorthand properties (ES6)
const name = 'Carol';
const age = 25;
const person = { name, age }; // same as { name: name, age: age }
console.log(person); // { name: 'Carol', age: 25 }
// Computed property names
const fieldName = 'email';
const dynamic = { [fieldName]: 'carol@example.com' };
console.log(dynamic); // { email: 'carol@example.com' }
Checking Property Existence
const obj = { name: 'Alice', role: undefined };
// in operator β checks own + inherited properties
console.log('name' in obj); // true
console.log('role' in obj); // true (even though value is undefined)
console.log('toString' in obj); // true (inherited from Object.prototype)
// hasOwnProperty β own properties only
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('toString')); // false
// Object.hasOwn (ES2022 β preferred over hasOwnProperty)
console.log(Object.hasOwn(obj, 'name')); // true
console.log(Object.hasOwn(obj, 'email')); // false
// Optional chaining for safe nested access
const config = { db: { host: 'localhost' } };
console.log(config?.db?.port); // undefined (no error)
console.log(config?.cache?.host); // undefined (no error)
Object.freeze and Object.seal
| Operation | Object.freeze() | Object.seal() | Normal Object |
|---|---|---|---|
| Add properties | No | No | Yes |
| Delete properties | No | No | Yes |
| Change values | No | Yes | Yes |
| Check with | Object.isFrozen() | Object.isSealed() | β |
Object.freeze() only prevents changes to the top-level properties. Nested objects remain mutable. For a deep freeze you need a recursive utility function.
Interview Questions
- What is the difference between dot notation and bracket notation?
- Why does
thisbehave differently in arrow functions vs regular methods? - What is the difference between
Object.freeze()andObject.seal()? - How do you check if a property exists on an object vs its prototype?
- What is the output of
Object.keys({ a: 1, b: undefined })? - How does spread-based object copying differ from
Object.assign?
ποΈ Practical Exercise
Create a bankAccount object with properties owner (string), balance (number), and methods deposit(amount), withdraw(amount) (prevent negative balance), and statement() (returns a formatted string). Test all three methods.
π₯ Challenge Exercise
Write a function deepFreeze(obj) that recursively freezes an object and all of its nested objects so that no property at any depth can be modified. Ensure it handles arrays and circular references safely.
Frequently Asked Questions
- Is
nullan object? typeof null === 'object'is a historic JavaScript bug.nullis not an object; it represents the intentional absence of any value. Always check fornullexplicitly:if (value !== null && typeof value === 'object').- What is the difference between
Object.assignand spread? - Both perform shallow merges.
Object.assignmutates the target object; spread always creates a new object. Spread is generally preferred in modern code for its clarity. - Can object keys be numbers?
- Keys are always coerced to strings (or Symbols).
obj[1]andobj['1']access the same property. Use aMapif you need non-string keys. - How do I deeply clone an object?
- Use
structuredClone(obj)(available in Node 17+ and modern browsers). Alternatively,JSON.parse(JSON.stringify(obj))works for plain data but drops functions,undefined, and Dates. - What does
Object.fromEntriesdo? - It converts an iterable of
[key, value]pairs into a plain object. It is the inverse ofObject.entries()and is useful for transforming objects via array methods.
π Summary
- Object literals
{}are the standard way to create objects in JavaScript. - Use dot notation for known keys; bracket notation for dynamic or special-character keys.
- Methods defined with regular function syntax can use
thisto reference the object. Object.keys/values/entriesconvert an object to iterable arrays.- Spread
{...obj}creates a shallow copy; nested objects are still shared references. - Use shorthand properties and computed property names for cleaner ES6+ code.
inchecks own and inherited properties;Object.hasOwnchecks own only.Object.freezeprevents all modifications;Object.sealprevents add/delete only.