JavaScript Objects: The Definitive Guide
Master JavaScript objects from creation to advanced patterns. Learn properties, methods, prototypes, inheritance, and object-oriented programming.
JavaScript Objects: The Definitive Guide
Objects are the foundation of JavaScript. Almost everything in JavaScript is an object or behaves like one. This comprehensive guide will teach you everything about objects, from basic creation to advanced patterns.
What is an Object?
An object is a collection of key-value pairs. The keys (also called properties) are strings, and the values can be any JavaScript data type.
// Simple object
const person = {
name: 'John Doe',
age: 30,
isEmployed: true,
};
console.log(person.name); // 'John Doe'
console.log(person['age']); // 30
Creating Objects
Object Literal
The most common way to create objects:
const user = {
firstName: 'Jane',
lastName: 'Doe',
age: 25,
email: 'jane@example.com',
// Method
getFullName: function () {
return `${this.firstName} ${this.lastName}`;
},
};
// ES6 method shorthand
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
};
Object Constructor
// Using new Object()
const obj1 = new Object();
obj1.name = 'John';
obj1.age = 30;
// Not recommended, use object literal instead
Object.create()
Creates a new object with specified prototype:
const personPrototype = {
greet() {
return `Hello, I'm ${this.name}`;
},
};
const john = Object.create(personPrototype);
john.name = 'John';
console.log(john.greet()); // "Hello, I'm John"
// With property descriptors
const jane = Object.create(personPrototype, {
name: {
value: 'Jane',
writable: true,
enumerable: true,
configurable: true,
},
age: {
value: 25,
writable: false,
},
});
Constructor Functions
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function () {
return `Hello, I'm ${this.name}`;
};
}
const person1 = new Person('John', 30);
const person2 = new Person('Jane', 25);
console.log(person1.greet()); // "Hello, I'm John"
console.log(person2.greet()); // "Hello, I'm Jane"
ES6 Classes
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Hello, I'm ${this.name}`;
}
static createGuest() {
return new User('Guest', 'guest@example.com');
}
}
const user = new User('John', 'john@example.com');
const guest = User.createGuest();
Accessing and Modifying Properties
Dot Notation vs Bracket Notation
const obj = {
name: 'John',
'favorite color': 'blue',
123: 'numeric key',
};
// Dot notation
console.log(obj.name); // 'John'
obj.age = 30;
// Bracket notation (required for special cases)
console.log(obj['favorite color']); // 'blue'
console.log(obj[123]); // 'numeric key'
// Dynamic property access
const prop = 'name';
console.log(obj[prop]); // 'John'
// Computed property names
const key = 'dynamic';
const obj2 = {
[key]: 'value',
[`${key}2`]: 'value2',
};
Adding and Deleting Properties
const person = { name: 'John' };
// Adding properties
person.age = 30;
person['city'] = 'New York';
// Deleting properties
delete person.city;
console.log(person.city); // undefined
// Checking if property exists
console.log('age' in person); // true
console.log(person.hasOwnProperty('age')); // true
Object Methods
Object.keys(), Object.values(), Object.entries()
const person = {
name: 'John',
age: 30,
city: 'New York',
};
// Get all keys
console.log(Object.keys(person));
// ['name', 'age', 'city']
// Get all values
console.log(Object.values(person));
// ['John', 30, 'New York']
// Get key-value pairs
console.log(Object.entries(person));
// [['name', 'John'], ['age', 30], ['city', 'New York']]
// Iterate over entries
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
Object.assign()
// Shallow copy
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original);
// Merge objects
const target = { a: 1, b: 2 };
const source1 = { b: 3, c: 4 };
const source2 = { c: 5, d: 6 };
Object.assign(target, source1, source2);
console.log(target); // { a: 1, b: 3, c: 5, d: 6 }
// Clone with modifications
const user = { name: 'John', age: 30 };
const updatedUser = Object.assign({}, user, { age: 31 });
Object Spread Operator (ES6)
// Copying objects
const original = { a: 1, b: 2 };
const copy = { ...original };
// Merging objects
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }
// Adding/overriding properties
const user = { name: 'John', age: 30 };
const updatedUser = { ...user, age: 31, city: 'NY' };
// Nested spread
const nested = {
a: 1,
b: { c: 2, d: 3 },
};
const shallowCopy = { ...nested }; // b is still referenced
Property Descriptors
Getting and Setting Descriptors
const obj = { name: 'John' };
// Get property descriptor
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
// {
// value: 'John',
// writable: true,
// enumerable: true,
// configurable: true
// }
// Define property with descriptor
Object.defineProperty(obj, 'age', {
value: 30,
writable: false, // Cannot be changed
enumerable: true, // Shows up in for...in
configurable: false, // Cannot be deleted or redefined
});
obj.age = 31; // Silently fails (strict mode throws error)
console.log(obj.age); // 30
Multiple Properties
const obj = {};
Object.defineProperties(obj, {
name: {
value: 'John',
writable: true,
enumerable: true,
},
age: {
value: 30,
writable: false,
enumerable: true,
},
_id: {
value: 123,
enumerable: false, // Hidden from enumeration
},
});
console.log(Object.keys(obj)); // ['name', 'age'] - _id is hidden
Getters and Setters
const person = {
firstName: 'John',
lastName: 'Doe',
// Getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// Setter
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
},
};
console.log(person.fullName); // 'John Doe'
person.fullName = 'Jane Smith';
console.log(person.firstName); // 'Jane'
console.log(person.lastName); // 'Smith'
// Using Object.defineProperty
const user = {
_age: 0,
};
Object.defineProperty(user, 'age', {
get() {
return this._age;
},
set(value) {
if (value < 0) {
throw new Error('Age cannot be negative');
}
this._age = value;
},
});
Object Freezing and Sealing
Object.freeze()
const obj = {
name: 'John',
age: 30,
address: {
city: 'New York',
},
};
Object.freeze(obj);
// Cannot modify
obj.name = 'Jane'; // Silently fails
obj.newProp = 'value'; // Silently fails
delete obj.age; // Silently fails
// But nested objects are not frozen
obj.address.city = 'Boston'; // This works!
// Deep freeze function
function deepFreeze(obj) {
Object.freeze(obj);
Object.values(obj).forEach((value) => {
if (typeof value === 'object' && value !== null) {
deepFreeze(value);
}
});
return obj;
}
Object.seal()
const obj = { name: 'John', age: 30 };
Object.seal(obj);
// Can modify existing properties
obj.name = 'Jane'; // Works
obj.age = 31; // Works
// Cannot add or delete properties
obj.city = 'New York'; // Silently fails
delete obj.age; // Silently fails
// Check if sealed
console.log(Object.isSealed(obj)); // true
Object.preventExtensions()
const obj = { name: 'John' };
Object.preventExtensions(obj);
// Can modify existing
obj.name = 'Jane'; // Works
// Can delete
delete obj.name; // Works
// Cannot add new
obj.age = 30; // Silently fails
console.log(Object.isExtensible(obj)); // false
Prototypes and Inheritance
Understanding Prototypes
// Every object has a prototype
const obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// Constructor function prototype
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hello, I'm ${this.name}`;
};
const john = new Person('John');
console.log(john.greet()); // "Hello, I'm John"
// Prototype chain
console.log(john.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
Prototypal Inheritance
// Base constructor
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
return `${this.name} makes a sound`;
};
// Derived constructor
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Add methods to derived prototype
Dog.prototype.bark = function () {
return `${this.name} barks!`;
};
// Override parent method
Dog.prototype.speak = function () {
return `${this.name} barks loudly!`;
};
const myDog = new Dog('Max', 'Golden Retriever');
console.log(myDog.speak()); // "Max barks loudly!"
console.log(myDog.bark()); // "Max barks!"
ES6 Class Inheritance
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
bark() {
return `${this.name} barks!`;
}
// Override parent method
speak() {
return super.speak() + ' - Woof!';
}
}
const myDog = new Dog('Max', 'Golden Retriever');
console.log(myDog.speak()); // "Max makes a sound - Woof!"
Object Composition
// Composition over inheritance
const canSwim = {
swim() {
return `${this.name} is swimming`;
},
};
const canFly = {
fly() {
return `${this.name} is flying`;
},
};
const canWalk = {
walk() {
return `${this.name} is walking`;
},
};
// Factory function with composition
function createDuck(name) {
return Object.assign({ name }, canSwim, canFly, canWalk);
}
const duck = createDuck('Donald');
console.log(duck.swim()); // "Donald is swimming"
console.log(duck.fly()); // "Donald is flying"
Advanced Object Patterns
Factory Pattern
function createUser(name, email, role = 'user') {
return {
name,
email,
role,
login() {
console.log(`${this.name} logged in`);
},
logout() {
console.log(`${this.name} logged out`);
},
hasPermission(action) {
const permissions = {
user: ['read'],
admin: ['read', 'write', 'delete'],
};
return permissions[this.role].includes(action);
},
};
}
const admin = createUser('John', 'john@example.com', 'admin');
console.log(admin.hasPermission('delete')); // true
Module Pattern
const Calculator = (function () {
// Private variables
let result = 0;
let history = [];
// Private function
function addToHistory(operation) {
history.push(operation);
}
// Public API
return {
add(n) {
result += n;
addToHistory(`+${n}`);
return this;
},
subtract(n) {
result -= n;
addToHistory(`-${n}`);
return this;
},
getResult() {
return result;
},
getHistory() {
return [...history]; // Return copy
},
reset() {
result = 0;
history = [];
return this;
},
};
})();
Calculator.add(5).subtract(2).add(10);
console.log(Calculator.getResult()); // 13
console.log(Calculator.getHistory()); // ['+5', '-2', '+10']
Singleton Pattern
const Database = (function () {
let instance;
function createInstance() {
return {
connection: null,
connect(connectionString) {
this.connection = connectionString;
console.log(`Connected to ${connectionString}`);
},
disconnect() {
this.connection = null;
console.log('Disconnected');
},
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true - same instance
Object Destructuring
// Basic destructuring
const user = {
name: 'John',
age: 30,
email: 'john@example.com',
};
const { name, age } = user;
console.log(name, age); // 'John' 30
// Renaming variables
const { name: userName, email: userEmail } = user;
// Default values
const { phone = 'N/A' } = user;
// Nested destructuring
const person = {
name: 'John',
address: {
city: 'New York',
country: 'USA',
},
};
const {
address: { city },
} = person;
console.log(city); // 'New York'
// Rest properties
const { name: personName, ...rest } = person;
console.log(rest); // { address: { city: 'New York', country: 'USA' } }
// Function parameters
function greet({ name, age = 18 }) {
return `Hello ${name}, you are ${age} years old`;
}
greet(user); // "Hello John, you are 30 years old"
Working with JSON
// Object to JSON
const obj = {
name: 'John',
age: 30,
hobbies: ['reading', 'gaming'],
address: {
city: 'New York',
country: 'USA',
},
};
const json = JSON.stringify(obj);
console.log(json); // String representation
// Pretty printing
const prettyJson = JSON.stringify(obj, null, 2);
// Custom serialization
const customJson = JSON.stringify(obj, (key, value) => {
if (key === 'age') return undefined; // Exclude age
return value;
});
// JSON to Object
const parsed = JSON.parse(json);
// Parsing with reviver
const dateJson = '{"date":"2023-01-01T00:00:00.000Z"}';
const objWithDate = JSON.parse(dateJson, (key, value) => {
if (key === 'date') return new Date(value);
return value;
});
Performance Considerations
// Object property access performance
const obj = { a: 1, b: 2, c: 3 };
// Fastest: dot notation with known property
console.time('dot');
for (let i = 0; i < 1000000; i++) {
const x = obj.a;
}
console.timeEnd('dot');
// Slower: bracket notation
console.time('bracket');
for (let i = 0; i < 1000000; i++) {
const x = obj['a'];
}
console.timeEnd('bracket');
// Hidden classes optimization
// Good - consistent property order
function Point(x, y) {
this.x = x;
this.y = y;
}
// Bad - inconsistent property order
function BadPoint(x, y) {
if (x > 0) {
this.x = x;
this.y = y;
} else {
this.y = y;
this.x = x;
}
}
Best Practices
- Use object literals for simple objects
- Prefer
const
for object declarations - Use descriptive property names
- Consider using classes for complex objects
- Avoid modifying prototypes of built-in objects
- Use Object.freeze() for immutable objects
- Be careful with nested object references
Conclusion
JavaScript objects are incredibly flexible and powerful. Key concepts to remember:
- Objects are collections of key-value pairs
- Multiple ways to create objects (literal, constructor, class)
- Properties can be accessed, added, and deleted dynamically
- Prototypes enable inheritance and method sharing
- Modern features like destructuring and spread operator simplify code
- Understanding objects is crucial for JavaScript mastery
Master these concepts to write more effective object-oriented JavaScript!