Skip to content

Objects

docs.scrimba.com

Say you are describing a single user: a name, an email, an age, whether they are signed in. You could scatter those into four separate variables, but they belong together, and keeping them apart means passing four things around every time you mean "the user". An object lets you gather all of it into one value, where each piece has a name you can look up. Most of the data you will handle, a user, a product, a cart, arrives shaped exactly like this.

Making an object

An object is a collection of named values, written with curly braces. Inside, you list key: value pairs, separated by commas. The key is the name; the value is what it holds:

js
const user = {
  name: "Priya",
  age: 27,
};

console.log(user.name); // Priya

You read a value back with a dot and the key: user.name. This is called dot notation, and it is what you will reach for most. The key on the left, the value it stored on the right.

There is a second way to read a value, using square brackets with the key as a string:

js
console.log(user["name"]); // Priya

Most of the time dot notation reads cleaner, so use it. Brackets earn their place when the key is not a plain word (say it has a space in it) or when you do not know the key ahead of time and it is sitting in another variable.

An object groups related values under string keys, written as key: value pairs inside curly braces. Where an array holds an ordered list you reach by position, an object holds values you reach by name:

js
const user = {
  name: "Priya",
  age: 27,
  isActive: true,
};

console.log(user.name); // Priya
console.log(user["age"]); // 27

There are two ways in. Dot notation (user.name) is the default: shorter and clearer. Bracket notation (user["age"]) does the same job but takes the key as a string, which unlocks two things dot notation cannot do.

The bracket form is required when the key is not a valid identifier, for example when it contains a space or starts with a digit, and when the key is dynamic, sitting in a variable rather than typed literally:

js
const field = "name";
console.log(user[field]); // Priya, reads the key held in `field`
console.log(user.field); // undefined, looks for a literal key named "field"

That difference trips people up: user[field] uses the value of field, while user.field looks for a key spelled field. Reading a key that does not exist returns undefined rather than an error.

An object is JavaScript's core structure for grouping values under keys. Keys are strings (or Symbols, which you can set aside for now); a value can be anything, including another object or a function. You write it as key: value pairs in curly braces:

js
const user = {
  name: "Priya",
  age: 27,
  isActive: true,
};

console.log(user.name); // Priya
console.log(user["age"]); // 27

Dot notation is the idiomatic default. Bracket notation is the same lookup with the key computed as an expression, which is why it is required in exactly two cases: when the key is not a valid identifier (user["first name"], user["2fa"]), and when the key is dynamic:

js
const field = "name";
console.log(user[field]); // Priya, evaluates `field` to "name" first

Two behaviours are worth holding onto. Reading a missing key returns undefined, never throws, so a typo fails quietly rather than loudly. And keys are always strings under the hood: user[27] and user["27"] reach the same slot, because the number is coerced to a string key. That coercion is a common source of confusion when object keys and array indices start looking alike.

js
const product = {
  title: "Desk lamp",
  price: 40,
  inStock: true,
};

console.log(`${product.title} costs ${product.price}`); // Desk lamp costs 40

An object is a value like any other. You can store it in a const, put it in an array, pass it to a function, and return it. The keys give its parts names, which is the whole point: product.price says what it is, where a bare 40 sitting in a variable does not.

JunoMaking an object An object gathers related values under names, written as key: value pairs in curly braces. Read a value with a dot and the key, like user.name. Reach for the square-bracket form only when the key has an odd character in it or lives in a variable.
JunoMaking an object An object reaches values by name where an array reaches them by position. Dot notation is your default; bracket notation takes the key as a string, so use it for keys that are not plain words or that live in a variable. Remember user[field] reads the key stored in field, while user.field looks for a literal field key, and a missing key comes back as undefined instead of erroring.
JunoMaking an object Keys are strings under the hood, so user[27] and user["27"] hit the same slot, and a missing key returns undefined rather than throwing. Dot notation is idiomatic; bracket notation is the same lookup with a computed key, needed for non-identifier keys and dynamic ones. That quiet undefined on a typo is convenient right up until it hides a bug.

Changing an object

Objects are not fixed once you make them. You can add a new key, change an existing one, or remove one entirely. To add or update, assign to the key:

js
const user = {
  name: "Priya",
};

user.age = 30; // adds a new key
user.name = "Priya Sharma"; // updates the existing one

console.log(user); // { name: "Priya Sharma", age: 30 }

If the key already exists, assigning to it replaces the value. If it does not, it gets created. To remove a key, use delete:

js
delete user.age;
console.log(user); // { name: "Priya Sharma" }

Here is the part that surprises people. A const object can still have its properties changed. const means the name user cannot point at a different object, but the object it points at is fair game:

js
const cart = { total: 0 };
cart.total = 25; // fine, changing what is inside
// cart = { total: 25 }; // this would error, reassigning the name

Objects are mutable: you add, update, and remove keys after creation. Assigning to a key creates it if it is new and overwrites it if it exists. Removing a key is the job of delete:

js
const user = { name: "Priya" };

user.age = 30; // add
user.age = 31; // update
delete user.age; // remove

console.log(user); // { name: "Priya" }

The one that catches people is const. const locks the name, not the contents. The binding cannot be reassigned to a new object, but the object it holds stays fully editable:

js
const cart = { total: 0, items: 0 };
cart.total = 25; // allowed, mutating the object
cart.items += 1; // allowed
// cart = {}; // TypeError, reassigning a const binding

This is why const is the sensible default even for objects you plan to change. You almost never want to repoint the variable, and const stops you doing it by accident while leaving the object itself open.

Objects are mutable. Assignment to a key upserts it (creates or overwrites), and delete removes it:

js
const user = { name: "Priya", age: 30 };

user.role = "admin"; // add
delete user.age; // remove

console.log(user); // { name: "Priya", role: "admin" }

The const distinction is the one to internalise, because it is really about what const protects. const freezes the binding, never the value. The name cannot be reassigned; the object it references stays open:

js
const cart = { total: 0 };
cart.total = 25; // fine, mutating the referenced object
// cart = { total: 25 }; // TypeError: Assignment to constant variable

If you actually want the object itself locked, Object.freeze(obj) prevents adding, changing, or removing keys, though it is a shallow freeze (nested objects stay mutable) and quietly does nothing in non-strict code rather than erroring. In practice you rarely need it. The habit worth keeping is const by default: it signals the binding is stable and blocks accidental reassignment, which is the mistake that actually happens, while leaving normal mutation available.

JunoChanging an object Assign to a key to add or update it, and use delete to remove one. The surprise is const: it stops you pointing user at a different object, but you can still change what is inside the one it holds. Took me a while to stop expecting const to lock everything.
JunoChanging an object Assignment adds or overwrites a key, delete removes one. const locks the name, not the contents, so a const object stays fully editable while the variable itself cannot be repointed. That is exactly why const is a fine default even for objects you mean to change.
JunoChanging an object Assignment upserts a key, delete removes one, and const protects the binding rather than the value, so the referenced object stays mutable. If you must lock the object, Object.freeze does it shallowly and fails silently outside strict mode. Reach for it rarely; const by default already stops the reassignment mistake that actually shows up.

Methods and this

A value in an object can be a function. When it is, it is called a method: something the object can do, rather than a fact it stores. You call it with the same dot, plus parentheses:

js
const user = {
  name: "Priya",
  greet() {
    return `Hi, I am ${this.name}`;
  },
};

console.log(user.greet()); // Hi, I am Priya

The new word inside is this. When you call user.greet(), this means "the object this method was called on", which here is user. So this.name reads the name key from the same object. That is how a method reaches its own object's values without you passing them in.

When a function is stored as a value on an object, it is a method. Methods let an object carry behaviour alongside its data, and they reach that data through this:

js
const cart = {
  items: ["lamp", "desk"],
  total: 65,
  summary() {
    return `${this.items.length} items, ${this.total} total`;
  },
};

console.log(cart.summary()); // 2 items, 65 total

The rule for this that covers almost everything you will hit: this is the object to the left of the dot at call time. Call cart.summary() and this is cart, so this.items and this.total read from the same object.

The phrase "at call time" matters. this is not fixed when you write the method, it is decided by how the method is called. Call it as cart.summary() and this is cart; pull the function out and call it on its own and this no longer points at cart. You will meet that edge later, but for methods called with a dot, this behaves exactly as you would hope.

A function stored on an object is a method, and it accesses the object's own state through this. The binding rule that matters in practice: this is set by how a function is called, not where it is defined. For a normal method call, this is the object left of the dot:

js
const cart = {
  total: 65,
  discount: 0.1,
  finalPrice() {
    return this.total * (1 - this.discount);
  },
};

console.log(cart.finalPrice()); // 58.5

Because the binding is dynamic, detaching the method breaks it: const fn = cart.finalPrice; fn(); loses the cart context and this no longer points where you want.

The practical gotcha is arrow functions as methods. An arrow does not get its own this; it captures the this from the surrounding scope where it was defined. So an arrow used as a method reads the outer this (at the top level, not the object) rather than the object it lives on:

js
const user = {
  name: "Priya",
  greet: () => `Hi, I am ${this.name}`, // wrong: `this` is not `user`
  greetOk() {
    return `Hi, I am ${this.name}`; // right: method shorthand
  },
};

The rule to carry: use method shorthand (greet() {}) when the function needs this to mean the object, and reserve arrows for callbacks where you actually want the surrounding this, which is most other places.

JunoMethods and this A function stored on an object is a method, called with a dot and parentheses like user.greet(). Inside it, this means the object the method was called on, so this.name reads that object's own name. That is how a method gets at its own object's values.
JunoMethods and this A method is a function living on an object, and this is the object to the left of the dot when you call it. So cart.summary() makes this mean cart. The catch is that this is decided at call time, not when you write the method, which matters the moment you pull a method off its object.
JunoMethods and thisthis is bound by how a function is called, not where it lives, so detaching a method loses its object. Use method shorthand when you need this to mean the object, and keep arrow functions for callbacks where you want the surrounding this, since an arrow captures the outer one instead. That arrow-as-method mix-up is a classic, and it fails quietly.

Objects and arrays together

Most real data is a list of objects: many users, many products, each object one record with the same keys. You put objects inside an array:

js
const products = [
  { title: "Desk lamp", price: 40 },
  { title: "Notebook", price: 6 },
  { title: "Pen", price: 3 },
];

console.log(products[0].title); // Desk lamp

You reach one object by its position (products[0]), then one of its fields with a dot (.title). Chained together, products[0].title reads the title of the first product.

To read a field from every object, loop over the array:

js
for (const product of products) {
  console.log(product.title);
}
// Desk lamp
// Notebook
// Pen

Each turn of the loop, product is one object from the list, and product.title reads its title. This shape, an array of objects you loop over, is most of the data work you will do.

An array of objects is the shape of most real data: each object a record, each sharing the same keys. Combine array access with dot access to reach any single field:

js
const products = [
  { title: "Desk lamp", price: 40 },
  { title: "Notebook", price: 6 },
  { title: "Pen", price: 3 },
];

console.log(products[1].price); // 6

Because it is an array, array methods work on it, and this is where they earn their keep. Pull one field from each object with map, keep matching records with filter, roll them into one value with reduce:

js
const titles = products.map((product) => product.title);
console.log(titles); // ["Desk lamp", "Notebook", "Pen"]

const total = products.reduce((sum, product) => sum + product.price, 0);
console.log(total); // 49

The callback receives one object per iteration, and you read its fields by key. Once this clicks, "for each record, take this field" and "total up this field across all records" become one-liners.

An array of objects is the canonical shape of structured data, and array methods are how you work it. Each callback receives one record; you read its fields by key and transform, filter, or aggregate:

js
const orders = [
  { id: 1, total: 40, paid: true },
  { id: 2, total: 6, paid: false },
  { id: 3, total: 3, paid: true },
];

const paidTotal = orders
  .filter((order) => order.paid)
  .reduce((sum, order) => sum + order.total, 0);

console.log(paidTotal); // 43

Chaining reads top to bottom as a pipeline: keep the paid orders, then sum their totals. This composes cleanly because each step takes an array of objects and returns another array (or, for reduce, a single value).

One thing to keep in mind as data grows: these objects are held by reference, which the advanced section below makes precise. Mutating an object inside a map or forEach callback changes the original record in the array, not a copy, which is occasionally what you want and often a surprise. When you mean to transform without touching the source, build new objects rather than editing the ones you were handed.

JunoObjects and arrays together Most real data is an array of objects: a list where each item is one record with the same keys. Reach one with products[0], then a field with products[0].title. Loop over the array to touch every record, and you have the pattern behind most of the data work you will do.
JunoObjects and arrays together An array of objects is the shape most real data arrives in, and array methods are built for it. map pulls a field from each record, filter keeps matching ones, reduce totals across them, with the callback handed one object per turn. "For each record, take this field" stops being a loop and becomes a one-liner.
JunoObjects and arrays together Array methods chain into a pipeline over records: filter down, then reduce to a value, each step reading fields by key. Watch that these objects are held by reference, so mutating one inside a map or forEach callback edits the original in place. When you mean to transform without touching the source, build new objects instead of editing the ones you were given.

Where objects go next

Objects and arrays are the two structures you will reach for constantly, and most data you handle is some nesting of the two: objects with array fields, arrays of objects, and combinations of both. Pair them with array methods and you can shape almost any data into the form you need.

The next step is putting objects to work in a page. When you reach the DOM, the elements you read and change are objects too, with keys you set and methods you call, so everything here carries straight over.