JS Objects & Object Methods
Welcome to Module 2: Objects, Arrays & Collections! In this chapter, we master JavaScript's most fundamental composite data structure: Objects. Explore key-value storage, heap references, dot vs bracket access, method design, the this context, destructuring, optional chaining (?.), and built-in static utilities.
Table of Contents
11.1 Object Fundamentals & Memory Architecture
In JavaScript, almost everything is an object (or can behave like one). While primitive types (numbers, strings, booleans) store a single atomic value directly on the execution Call Stack, an Object is a complex, non-primitive reference type that stores an unordered collection of keyed values inside the Memory Heap.
Primitives by Value vs Objects by Reference
When you assign a variable to an object: let user = { name: "Alex" };, the variable user does not hold the object data itself. Instead, it holds a memory pointer (reference address) pointing to where the object lives in the system heap. If you assign let admin = user;, both variables point to the exact same object in memory!
Object Creation Patterns
// 1. Object Literal Syntax (Preferred & Idiomatic)
const user = {
name: "Alex Vance",
age: 28,
role: "Lead Engineer",
isActive: true
};
// 2. Object Constructor (Legacy / Verbose)
const legacyObj = new Object();
legacyObj.platform = "AICodeLab";
// 3. Object.create (Prototypal Inheritance Foundation)
const prototypeBase = { kind: "entity" };
const specializedEntity = Object.create(prototypeBase);
specializedEntity.id = 901;
Try it Yourself »
11.2 Accessing & Modifying Properties (Dot vs Bracket Notation)
JavaScript provides two fundamental syntaxes for accessing and modifying an object's properties: Dot Notation and Bracket Notation. Knowing when to use which is essential for dynamic programming.
| Feature | Dot Notation (obj.property) |
Bracket Notation (obj['property']) |
|---|---|---|
| Syntax Simplicity | Clean, concise, and easiest to read. | Requires quotes around literal string property keys. |
| Variable Key Resolution | ❌ No. Evaluates property literally as identifier. | ✅ Yes: obj[variableKey] evaluates variable. |
| Special Characters & Spaces | ❌ Invalid identifier (e.g. user.first-name errors). |
✅ Fully supported (e.g. user['first-name']). |
| Numeric / Reserved Keys | ❌ Syntax error for numbers (e.g. obj.1). |
✅ Supported: obj[1] automatically stringified to obj['1']. |
Dot vs Bracket, Dynamic Keys, and Deletion
const serverConfig = {
host: "db.aicode.internal",
"max-connections": 250,
port: 5432
};
// 1. Reading
console.log(serverConfig.host); // "db.aicode.internal"
console.log(serverConfig["max-connections"]); // 250 (bracket notation mandatory)
// 2. Dynamic Access with Variables
const dynamicField = "port";
console.log(serverConfig[dynamicField]); // 5432
// 3. Computed Property Names (ES6)
const env = "prod";
const dynamicConfig = {
[env + "_apiKey"]: "LIVE_SEC_9921",
[env + "_region"]: "us-east-1"
};
console.log(dynamicConfig.prod_apiKey); // "LIVE_SEC_9921"
// 4. Deleting Properties with 'delete'
delete serverConfig.port;
console.log(serverConfig.port); // undefined
Try it Yourself »
11.3 Property Inspection & The in Operator
A common bug in JavaScript is checking whether a property exists by comparing against undefined. If an object property exists and its value is intentionally set to undefined, obj.prop !== undefined will falsely report that the property is absent!
The 3 Ways to Check Property Existence
'prop' in obj: Returnstrueif the property exists on the object or anywhere on its prototype chain.obj.hasOwnProperty('prop'): Returnstrueonly if the property belongs directly to the object (ignores prototypes), but fails ifObject.create(null)was used.Object.hasOwn(obj, 'prop')(ES2022 Standard): The modern, safest replacement forhasOwnProperty. Works reliably even on objects withnullprototypes.
Testing Existence Reliably
const order = {
orderId: "ORD-9401",
discountCode: undefined // Property EXISTS, but value is undefined
};
// ❌ Flawed check:
console.log(order.discountCode !== undefined); // false (incorrectly implies key missing!)
// ✅ Check 1: The 'in' operator
console.log("discountCode" in order); // true
console.log("toString" in order); // true (inherited from Object.prototype)
// ✅ Check 2: Object.hasOwn() (ES2022 Modern Gold Standard)
console.log(Object.hasOwn(order, "discountCode")); // true
console.log(Object.hasOwn(order, "toString")); // false (ignores inherited prototype methods)
Try it Yourself »
11.4 Methods & The this Keyword Introduction
A function stored as an object property is called a method. Methods allow objects to act on their own encapsulated data using the special this keyword.
The Golden Rule of this in Object Methods
In regular JavaScript methods, this is determined at call-time by the object to the left of the dot. However, Arrow functions do NOT have their own this! They capture this lexically from their enclosing parent scope (usually the global window or module context). Therefore, never use an arrow function when defining an object literal method that needs this.
Regular Method vs Arrow Function Pitfall
const account = {
owner: "Sophia Chen",
balance: 1450,
// 1. ES6 Method Shorthand (Preferred)
deposit(amount) {
this.balance += amount;
return this.owner + "'s new balance: $" + this.balance;
},
// 2. Arrow Function Method (PITFALL!)
getBalanceArrow: () => {
// 'this' is NOT the account object! It resolves to window / undefined
return this.balance;
}
};
console.log(account.deposit(50)); // "Sophia Chen's new balance: $1500"
console.log(account.getBalanceArrow()); // undefined! (Cannot read property of window)
Try it Yourself »
this Binding Resolution Pipeline
11.5 Object Destructuring & Rest Properties
Introduced in ES6, Object Destructuring allows you to unpack properties from objects directly into distinct local variables using a clear, declarative syntax.
Destructuring, Defaults, Aliases & Rest
const developer = {
id: 402,
username: "marina_dev",
tier: "Gold",
contact: {
email: "marina@code.io",
city: "Seattle"
},
joinedYear: 2022
};
// 1. Basic Extraction with Default Fallback
const { username, role = "Contributor" } = developer;
console.log(username); // "marina_dev"
console.log(role); // "Contributor" (default used because role was undefined)
// 2. Renaming / Aliasing Keys
const { id: devId, tier: membershipLevel } = developer;
console.log(devId, membershipLevel); // 402, "Gold"
// 3. Deep Nested Destructuring
const { contact: { email, city } } = developer;
console.log(email, city); // "marina@code.io", "Seattle"
// 4. Rest Properties Pattern (...rest)
const { id, username: uname, ...publicProfile } = developer;
console.log(publicProfile);
// { tier: "Gold", contact: { email: "...", city: "..." }, joinedYear: 2022 }
Try it Yourself »
11.6 Safe Navigation: Optional Chaining (?.) & Nullish Coalescing
Before ES2020, traversing deeply nested objects received from APIs required endless defensive guards (e.g. user && user.profile && user.profile.address). Attempting to read a nested property on null or undefined crashed the application with:
TypeError: Cannot read properties of undefined (reading 'street').
The Optional Chaining Operator (?.)
The ?. operator immediately short-circuits evaluation and returns undefined if the operand preceding it is null or undefined, avoiding runtime exceptions. When paired with the Nullish Coalescing Operator (??), you can supply robust fallback values.
Optional Chaining with Objects, Methods & Brackets
const client = {
name: "Atlas Logistics",
preferences: {
theme: "dark"
// address node is omitted!
}
};
// ❌ Legacy crash:
// console.log(client.preferences.address.city); // Throws TypeError!
// ✅ Safe Property Access
const city = client.preferences?.address?.city;
console.log(city); // undefined (No crash!)
// ✅ Resilient Defaults with Nullish Coalescing (??)
const formattedCity = client.preferences?.address?.city ?? "Not Provided";
console.log(formattedCity); // "Not Provided"
// ✅ Optional Method Calling: obj.method?.()
client.sendAnalyticsReport?.(); // Safely does nothing if method does not exist
// ✅ Optional Bracket Access: obj?.[key]
const targetKey = "zipCode";
console.log(client.preferences?.address?.[targetKey] ?? "00000"); // "00000"
Try it Yourself »
11.7 Static Object Methods & Immutability
The global Object constructor provides essential static helper functions for reflection, transformation, cloning, and immutability controls.
| Method | Description | Example Return |
|---|---|---|
Object.keys(obj) |
Returns array of an object's own enumerable property names. | ['name', 'role'] |
Object.values(obj) |
Returns array of an object's own enumerable property values. | ['Alex', 'Admin'] |
Object.entries(obj) |
Returns array of [key, value] pairs (ideal for for...of loops). |
[['name', 'Alex'], ['role', 'Admin']] |
Object.freeze(obj) |
Complete lock: Prevents adding, deleting, or modifying existing properties. | Frozen, immutable object. |
Object.seal(obj) |
Partial lock: Prevents adding or deleting properties, but allows mutating existing values. | Sealed object. |
Inspecting and Freezing Objects
const systemSettings = {
maxUploadMB: 50,
allowGuests: false
};
// 1. Iterating with Object.entries()
for (const [key, val] of Object.entries(systemSettings)) {
console.log(key + " → " + val);
}
// 2. Freezing for Protection
Object.freeze(systemSettings);
systemSettings.maxUploadMB = 200; // Silently ignored in non-strict, throws in strict mode
systemSettings.newSetting = true; // Ignored
console.log(systemSettings.maxUploadMB); // 50 (Unchanged!)
console.log(Object.isFrozen(systemSettings)); // true
Try it Yourself »
11.8 Interactive Mini-Labs
Put memory references, the this keyword, and optional chaining into action with these 3 interactive real-time simulators:
Reference Pointer vs Spread Clone Simulator
Observe how mutating an object alias alters the original object when assigned by reference (b = a), compared to when cloned via spread (b = { ...a }).
{
name: "Morgan",
score: 100
}
{
name: "Morgan",
score: 100
}
a and b share the exact same heap memory address (0x4A10). Mutating b also mutates a!
The this Context Inspector
Test the runtime resolution of the this keyword between standard ES6 object methods and arrow function methods.
this bound to the calling object. Arrow functions inherit this lexically.
Deep Optional Chaining (?.) & Crash Prevention
Simulate reading nested profile properties from an asynchronous API payload where sub-branches might be null or undefined.
11.9 Coding Challenge: Secure User Profile Sanitizer
The Mission
You are engineering an authentication gateway. When external third-party identity providers send raw user data payloads, they often contain sensitive private keys (e.g. passwordHash, internalToken) and missing nested profile fields.
Write a function sanitizeUserProfile(rawInput) that:
- Safely extracts
id,email, and nestedprofile.displayNameusing destructuring and optional chaining (with sensible defaults if missing). - Uses the object rest pattern (
...rest) to isolate and eliminate sensitive keys (passwordHash,internalToken). - Injects a dynamic computed property:
[ "sanitized_at_" + Date.now() ]: true. - Calls
Object.freeze()on the generated profile so downstream services cannot accidentally mutate the verified identity. - Returns the clean, frozen object.
/**
* Sanitizes and seals incoming user authentication payloads
* @param {Object} rawInput - External raw payload
* @returns {Readonly<Object>} - Frozen, safe user profile
*/
function sanitizeUserProfile(rawInput) {
// 1. Guard check for valid object
if (!rawInput || typeof rawInput !== "object") {
return Object.freeze({ valid: false, error: "Invalid payload input" });
}
// 2. Destructure known sensitive keys away from the rest of the payload
const {
passwordHash,
internalToken,
id = "anon_" + Math.random().toString(36).slice(2, 7),
email = "unspecified@user.local",
profile,
...otherProperties
} = rawInput;
// 3. Safe nested extraction via Optional Chaining and Nullish Coalescing
const displayName = profile?.displayName ?? email.split("@")[0] ?? "Guest User";
const avatarUrl = profile?.avatarUrl ?? "https://aicode.internal/assets/default-avatar.png";
// 4. Construct sanitized output with computed timestamp property
const timestampKey = "sanitized_at_" + Date.now();
const safeProfile = {
id,
email,
displayName,
avatarUrl,
customAttributes: { ...otherProperties },
[timestampKey]: true
};
// 5. Freeze to guarantee immutability across services
return Object.freeze(safeProfile);
}
// ── Test Execution ──
const rawIncomingUser = {
id: "usr_9918",
email: "clara.oswald@domain.com",
passwordHash: "$2b$10$e8Zb01k092kds012",
internalToken: "SYS_SECRET_KEY_EXPOSURE",
profile: {
displayName: "Clara O."
// avatarUrl is omitted
},
country: "UK",
role: "Editor"
};
const cleanUser = sanitizeUserProfile(rawIncomingUser);
console.log(cleanUser);
// Result: sensitive keys removed, avatarUrl has fallback, object is frozen!
// cleanUser.email = "hacked@evil.com"; // Silently fails or throws in strict mode!
Try it Yourself »
Object.freeze() enforces immutability at architectural boundaries.
11.10 Chapter Summary & Key Takeaways
Core Principles
- Objects are Reference Types: Variables store heap memory addresses, not the object data itself. Mutating an alias mutates all shared references.
- Dot vs Bracket: Use dot notation by default; use bracket notation (
obj[key]) for variables, numbers, or keys with hyphens/spaces. - Safe Property Inspection: Prefer ES2022
Object.hasOwn(obj, key)over checking forundefined.
Modern ES6+ Techniques
- The
thisContext: In regular methods,thisresolves to the invoking object. Arrow functions inheritthislexically and should not be used as methods. - Destructuring: Cleanly extracts fields, provides defaults (
{ role = 'user' }), and renames variables ({ id: userId }). - Optional Chaining (
?.): Safely traverses nested objects and preventsTypeErrorcrashes.