Objects: The Basics
Objects are one of the most important data types in JavaScript. They let you store collections of key-value pairs.
Objects
An object is created with curly braces {}:
let user = {
name: "Alice",
age: 30,
isAdmin: true
};
console.log(user.name); // β "Alice"
console.log(user.age); // β 30
Creating Objects
// Object literal
let person = {
firstName: "John",
lastName: "Doe"
};
// Using new Object()
let car = new Object();
car.brand = "Tesla";
car.model = "Model 3";
// Empty object, then add properties
let book = {};
book.title = "JavaScript Guide";
book.pages = 400;
Property Access
let user = { name: "Alice", age: 30 };
// Dot notation
console.log(user.name); // β "Alice"
// Bracket notation β for dynamic keys or invalid identifiers
console.log(user["name"]); // β "Alice"
let key = "age";
console.log(user[key]); // β 30
// Property names with spaces need brackets
user["full name"] = "Alice Johnson";
Computed Properties
You can use expressions as property names with brackets:
let fruit = "apple";
let bag = {
[fruit]: 5, // β apple: 5
[fruit + "s"]: 10 // β apples: 10
};
console.log(bag.apple); // β 5
console.log(bag.apples); // β 10
Property Value Shorthand
When variable and property names match, you can shorten:
let name = "Alice";
let age = 30;
// Long
let user1 = { name: name, age: age };
// Short (ES6)
let user2 = { name, age };
Property Existence Check
let user = { name: "Alice", age: 30 };
// Using in operator
console.log("name" in user); // β true
console.log("email" in user); // β false
// Using undefined check
console.log(user.email === undefined); // β true
// But careful β the property might exist with value undefined
user.email = undefined;
console.log("email" in user); // β true (property exists!)
console.log(user.email); // β undefined
forβ¦in Loop
Iterates over all enumerable keys:
let user = { name: "Alice", age: 30, isAdmin: true };
for (let key in user) {
console.log(`${key}: ${user[key]}`);
}
// β name: Alice
// β age: 30
// β isAdmin: true
Ordering of Properties
- Integer keys are sorted in ascending order
- Other keys are in insertion order
let codes = { "3": "three", "1": "one", "2": "two" };
for (let key in codes) {
console.log(key); // β "1", "2", "3" (sorted!)
}
let user = { name: "Alice", age: 30 };
for (let key in user) {
console.log(key); // β "name", "age" (insertion order)
}
let user = {
name: 'Alice',
age: 30,
greet() {
return 'Hi, I\'m ' + this.name;
}
};
document.write('User: ' + user.name + '<br>');
document.write('Age: ' + user.age + '<br>');
document.write(user.greet() + '<br>');
// Add new property
user.email = 'alice@example.com';
document.write('Email: ' + user.email + '<br>');
// List all properties
document.write('<br>All properties:<br>');
for (let key in user) {
document.write(key + ': ' + user[key] + '<br>');
} Object References and Copying
Primitives are copied βby valueβ:
let a = 10;
let b = a;
b = 20;
console.log(a); // β 10 (unchanged!)
console.log(b); // β 20
Objects are copied βby referenceβ:
let user1 = { name: "Alice" };
let user2 = user1; // Both point to the same object
user2.name = "Bob";
console.log(user1.name); // β "Bob" (changed!)
console.log(user2.name); // β "Bob"
Copying an Object (Cloning)
let user = { name: "Alice", age: 30 };
// Method 1: Object.assign (shallow copy)
let clone1 = Object.assign({}, user);
// Method 2: Spread operator (ES6)
let clone2 = { ...user };
// Method 3: Manual loop
let clone3 = {};
for (let key in user) {
clone3[key] = user[key];
}
Deep Cloning
For nested objects, shallow copy isnβt enough:
let user = {
name: "Alice",
address: {
city: "New York",
zip: 10001
}
};
let clone = { ...user };
clone.address.city = "Boston";
console.log(user.address.city); // β "Boston" (nested object still shared!)
Deep clone options:
// Method 1: JSON (works for simple data)
let deepClone = JSON.parse(JSON.stringify(user));
// Method 2: structuredClone (modern browsers, Node 17+)
let deepClone2 = structuredClone(user);
// Method 3: _.cloneDeep from Lodash
// import _ from 'lodash';
// let deepClone3 = _.cloneDeep(user);
Const objects
const prevents reassignment, but the object contents can change:
const user = { name: "Alice" };
user.name = "Bob"; // β
Allowed β modifying the object
// user = { name: "Charlie" }; // β Error β reassigning
Garbage Collection
JavaScript automatically manages memory. When an object becomes unreachable, it gets garbage collected.
let user = { name: "Alice" };
user = null; // The object is now unreachable β GC collects it
How GC Works (Mark-and-Sweep)
The garbage collector:
- Starts from roots (global objects, local variables, the call stack)
- Marks all objects reachable from roots
- Removes (sweeps) unmarked objects
function marry(a, b) {
a.spouse = b;
b.spouse = a;
}
let alice = { name: "Alice" };
let bob = { name: "Bob" };
marry(alice, bob); // They reference each other
alice = null; // Alice's object is no longer globally reachable
// Bob's object is still reachable from global "bob", so it stays
// But Alice's object is reachable from Bob (bob.spouse), so actually it stays too!
// If we also do:
bob = null; // Now the entire island is unreachable β both get collected
Memory Leaks
// β Accidental global variables
function createLeak() {
leakedVar = "I'm global now!"; // No let/const!
}
// β Forgotten timers
let data = getHugeData();
setInterval(() => {
console.log(data); // data can never be GC'd
}, 1000);
// β Detached DOM references
let element = document.getElementById("button");
document.body.removeChild(element);
// element still references the DOM node in memory
Object Methods, βthisβ
Functions stored in objects are called methods:
let user = {
name: "Alice",
greet: function() {
console.log("Hello!");
}
};
// Shorthand (ES6)
let user2 = {
name: "Bob",
greet() {
console.log("Hello!");
}
};
user.greet(); // β "Hello!"
The βthisβ Keyword
Inside a method, this refers to the object the method was called on:
let user = {
name: "Alice",
greet() {
console.log("Hello, " + this.name);
}
};
user.greet(); // β "Hello, Alice"
βthisβ is NOT bound to the function
In JavaScript, this is determined by how the function is called, not where itβs defined:
let user = { name: "Alice" };
let admin = { name: "Admin" };
function greet() {
console.log("Hello, " + this.name);
}
user.f = greet;
admin.f = greet;
user.f(); // β "Hello, Alice" (this = user)
admin.f(); // β "Hello, Admin" (this = admin)
Losing βthisβ
let user = {
name: "Alice",
greet() {
console.log("Hello, " + this.name);
}
};
let fn = user.greet;
fn(); // β "Hello, undefined" β this is lost!
// In non-strict mode: this β global object
// In strict mode: this β undefined
Arrow Functions have no βthisβ
Arrow functions inherit this from the surrounding (lexical) scope:
let user = {
name: "Alice",
greet() {
let arrow = () => console.log("Hello, " + this.name);
arrow(); // "Hello, Alice" β inherits this from greet()
}
};
user.greet();
// But with a regular function, this is lost:
let user2 = {
name: "Bob",
greet() {
let regular = function() {
console.log("Hello, " + this.name);
};
regular(); // "Hello, undefined"
}
};
user2.greet();
Constructor, operator βnewβ
Constructor functions create multiple objects with the same structure:
function User(name, age) {
// this = {} (implicitly created)
this.name = name;
this.age = age;
this.isAdmin = false;
// return this (implicitly)
}
let user1 = new User("Alice", 30);
let user2 = new User("Bob", 25);
console.log(user1.name); // β "Alice"
console.log(user2.name); // β "Bob"
How new works
- Creates a new empty object
{} - Sets
thisto that object - Executes the function body (modifies
this) - Returns
this
Constructor Convention
- Constructor names start with a capital letter
- They are called with
new
// Correct
let date = new Date();
// Constructors vs regular functions β constructors use new
function Car(brand) {
if (!new.target) {
// If called without new, we can still make it work
return new Car(brand);
}
this.brand = brand;
}
Constructor Return
Constructors usually donβt have a return. If they return an object, that object is used instead of this. If they return a primitive, itβs ignored:
function User(name) {
this.name = name;
return { name: "Override" }; // Returns this object, not this
}
console.log(new User("Alice").name); // β "Override"
Optional Chaining β?.β
Safely access nested properties without throwing if something is null/undefined:
let user = null;
// Without optional chaining β error!
// console.log(user.address.city); // TypeError!
// With optional chaining β safe
console.log(user?.address?.city); // β undefined (no error)
// For methods
let obj = null;
obj?.method(); // β undefined (safe)
// For dynamic access
let key = "name";
console.log(user?.[key]); // β undefined
// Optional chaining with delete
delete user?.address; // Safe
?.checks if the left side isnull/undefined. If it is, the expression short-circuits toundefined.
Symbol Type
Symbols are unique identifiers introduced in ES6:
let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1 === id2); // β false β each Symbol is unique!
console.log(id1); // β Symbol(id)
console.log(id1.description); // β "id"
Hidden Properties
Symbols let you add βhiddenβ properties that wonβt accidentally collide:
let user = { name: "Alice" };
let id = Symbol("id");
user[id] = 123; // Symbol-keyed property
console.log(user[id]); // β 123
// Symbols are skipped in for...in
for (let key in user) {
console.log(key); // Only β "name"
}
// Object.keys also skips symbols
console.log(Object.keys(user)); // β ["name"]
// But Object.getOwnPropertySymbols finds them
console.log(Object.getOwnPropertySymbols(user)); // β [Symbol(id)]
Global Symbols
If you need the same symbol across your app, use the global symbol registry:
let id1 = Symbol.for("id"); // Creates or retrieves global symbol
let id2 = Symbol.for("id"); // Retrieves the same symbol
console.log(id1 === id2); // β true
// Get key from global symbol
console.log(Symbol.keyFor(id1)); // β "id"
Object to Primitive Conversion
JavaScript automatically calls conversion methods when needed:
Hints
- string β when an object is used as a string (e.g., template literal)
- number β when doing math
- default β when the operator is unsure (e.g.,
+)
let user = {
name: "Alice",
age: 30,
// String conversion
toString() {
return `User: ${this.name}`;
},
// Numeric conversion
valueOf() {
return this.age;
}
};
console.log(String(user)); // β "User: Alice"
console.log(+user); // β 30
console.log(user + 5); // β 35 (uses valueOf)
Symbol.toPrimitive
A universal method for all conversion types:
let user = {
name: "Alice",
age: 30,
[Symbol.toPrimitive](hint) {
if (hint === "string") return `User: ${this.name}`;
if (hint === "number") return this.age;
return this.age; // default
}
};
console.log(`${user}`); // β "User: Alice" (string hint)
console.log(+user); // β 30 (number hint)
console.log(user + 5); // β 35 (default hint)