Objects Β· Astro Tech Blog

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)
}
Demo: Objects Demo
JavaScript
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>');
}
Live Output Window

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:

  1. Starts from roots (global objects, local variables, the call stack)
  2. Marks all objects reachable from roots
  3. 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

  1. Creates a new empty object {}
  2. Sets this to that object
  3. Executes the function body (modifies this)
  4. 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 is null/undefined. If it is, the expression short-circuits to undefined.

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)