Skip to main content

📦 Object Creation and Literals

Objects are how JavaScript models almost everything — a user, a shopping cart, a game entity, a config file. This lesson walks through every practical way to create one, from the humble curly-brace literal to full ES6 classes, and, just as importantly, when to reach for each.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Describe what a JavaScript object is and distinguish properties from methods
  • Create objects six ways: literals, the Object constructor, Object.create(), factory functions, constructor functions, and ES6 classes
  • Explain what the new keyword actually does, step by step
  • Access, add, and remove properties dynamically with dot and bracket notation
  • Choose the right creation pattern for a given situation

Estimated Time: 35–45 minutes  •  Difficulty: Intermediate

Hands-on: Build a small library-management system using the pattern that fits best.

In This Lesson

What Is a JavaScript Object?

An object is a collection of related data and behavior grouped under one name, stored as key–value pairs. A value that is data is called a property; a value that is a function is called a method.

💡 A useful analogy: Think of an object like a backpack. It holds items (properties — its color, its volume, the number of pockets) and it has things it can do (methods — open, close, adjust the straps). The backpack bundles the "what it is" together with the "what it does" into one tidy package you can carry around.
const backpack = {
  // Properties — data describing the object
  color: 'blue',
  volume: 30,
  pockets: 5,

  // Methods — functions that act on the object
  open() {
    console.log('Backpack is now open');
  },
  close() {
    console.log('Backpack is now closed');
  },
};

console.log(backpack.color); // 'blue'
backpack.open();             // 'Backpack is now open'

📖 Key Terms

Property: a named value stored on an object (color: 'blue').

Method: a property whose value is a function (open() { … }).

Instance: one concrete object built from a reusable blueprint like a class or constructor.

Object Literals

The simplest and by far the most common way to create an object is the object literal: a pair of curly braces holding comma-separated key–value pairs. No keyword, no ceremony.

const car = {
  make: 'Toyota',
  model: 'Corolla',
  year: 2024,
  isElectric: false,
  start() {
    console.log('Engine started!');
  },
};

JavaScript gives you six ways to create objects. Here is the map before we walk each path:

graph TD A[Creating a JavaScript Object] --> B[Object Literal] A --> C[new Object] A --> D[Object.create] A --> E[Factory Function] A --> F[Constructor Function] A --> G[ES6 Class] B --> B1[One-off / config] E --> E1[Many objects, no new] F --> F1[Many objects, with new] G --> G1[Modern blueprint syntax]

✅ When to use a literal

Reach for a literal when you need one object: a configuration object, a bundle of options passed to a function, or a quick return value. If you find yourself copy-pasting the same literal to make many similar objects, that is your signal to move up to a factory, constructor, or class.

The Object Constructor & Object.create()

new Object()

You can build an empty object with the built-in Object constructor and fill it in afterward. This is rarely used in modern code — a literal is shorter and clearer — but you will see it, so recognize it.

const person = new Object();
person.name = 'Maria';
person.age = 28;
person.greet = function () {
  console.log(`Hello, my name is ${this.name}`);
};

// Equivalent — and preferred — literal:
const person2 = {
  name: 'Maria',
  age: 28,
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  },
};

Object.create()

Object.create(proto) makes a new object whose prototype is the object you pass in. The new object inherits everything on that prototype. This is JavaScript's inheritance mechanism laid bare — you will explore it in depth in the Object Prototypes lesson.

const vehicleActions = {
  start() {
    console.log('Engine started!');
  },
  stop() {
    console.log('Engine stopped!');
  },
};

// myCar inherits start() and stop() from vehicleActions
const myCar = Object.create(vehicleActions);
myCar.make = 'Honda';
myCar.model = 'Civic';

myCar.start(); // 'Engine started!' — found on the prototype

Factory Functions

A factory function is an ordinary function that builds and returns a new object each time you call it. No new keyword required. Factories shine when you want to produce many similar objects and optionally keep some data private via closures.

function createUser(name, email, role) {
  return {
    name,          // shorthand: name: name
    email,
    role,
    createdAt: new Date(),
    isActive: true,
    login() {
      console.log(`${this.name} has logged in`);
    },
    logout() {
      console.log(`${this.name} has logged out`);
    },
  };
}

const alice = createUser('Alice', 'alice@example.com', 'admin');
const bob = createUser('Bob', 'bob@example.com', 'editor');

alice.login(); // 'Alice has logged in'

Notice the ES6 shorthand property names: because the parameter name and the property name match, you can write name once instead of name: name.

💡 Factories can keep secrets

Any variable declared inside the factory but not returned stays private — the returned methods can still see it through closure, but outside code cannot. This is a lightweight way to get truly private state without class #private fields.

Constructor Functions & the new Keyword

A constructor function is a regular function you call with new. By convention its name starts with a capital letter. It was the standard blueprint pattern before ES6 classes arrived.

function Product(name, price, category) {
  this.name = name;
  this.price = price;
  this.category = category;
  this.isInStock = true;
}

const laptop = new Product('MacBook Pro', 1299, 'Electronics');
const chair = new Product('Ergonomic Chair', 249, 'Furniture');

console.log(laptop.name); // 'MacBook Pro'

What does new actually do? Four things, in order:

What the new keyword does Four steps: create an empty object, bind this to it, link it to the constructor's prototype, and return it automatically. 1. Create empty {} object 2. Bind this = new object 3. Link to .prototype 4. Return the object
Figure 1 — The four steps new performs behind the scenes. If the function does not explicitly return its own object, step 4 hands back the one new created.

⚠️ Put shared methods on the prototype

If you assign methods inside the constructor with this.method = function () {…}, every instance gets its own copy of that function — wasteful when you have thousands of instances. Attach shared methods to Product.prototype instead, so all instances share one function object. ES6 classes do this for you automatically.

ES6 Classes

The class syntax, introduced in ES2015, is the modern, readable way to define a blueprint. It is syntactic sugar over constructor functions and prototypes — the same machinery underneath, a much friendlier surface on top. Methods you define in the class body land on the prototype automatically.

class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
    this.createdAt = new Date();
  }

  makeSound() {
    console.log('Some generic animal sound');
  }

  describe() {
    console.log(`${this.name} is a ${this.species}`);
  }
}

const dog = new Animal('Rex', 'dog');
dog.describe(); // 'Rex is a dog'

Classes also support inheritance with extends and super, letting a subclass reuse and specialize a parent. Here a digital product inherits everything from a general product and adds its own twist:

class Product {
  constructor(id, name, price) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.inventory = 0;
  }

  applyDiscount(percentage) {
    return this.price * (1 - percentage / 100);
  }
}

class DigitalProduct extends Product {
  constructor(id, name, price, fileSize) {
    super(id, name, price);   // run the parent constructor first
    this.fileSize = fileSize;
    this.inventory = Infinity; // digital goods never run out
  }

  downloadLink() {
    return `https://example.com/downloads/${this.id}`;
  }
}

const ebook = new DigitalProduct(102, 'The Good Parts (Digital)', 19.99, '4.2MB');
console.log(ebook.downloadLink());            // '.../downloads/102'
console.log(ebook.applyDiscount(15).toFixed(2)); // '16.99'

Console output

https://example.com/downloads/102
16.99

Accessing Properties Dynamically

Once you have an object, you read and write its properties two ways. Dot notation is for property names you know at author time. Bracket notation is for names held in a variable or names that are not valid identifiers (spaces, dashes).

const settings = {
  theme: 'dark',
  fontSize: 16,
  notifications: true,
};

// Dot notation — literal, known name
console.log(settings.theme); // 'dark'

// Bracket notation — the name comes from a variable
const key = 'fontSize';
console.log(settings[key]);  // 16

// Adding properties on the fly
settings.language = 'English';
settings['time-zone'] = 'UTC-5'; // dash forces bracket notation

// Removing a property
delete settings.notifications;

console.log('notifications' in settings); // false

💡 Why bracket notation matters

Bracket notation is what lets you write generic code — a function that reads obj[fieldName] where fieldName is passed in. Form handlers, table renderers, and config loaders all lean on it constantly.

Which Pattern Should I Use?

With six options, a quick decision guide helps. In modern code the honest answer is usually "a literal for one, a class for many," but knowing the whole toolbox lets you read any codebase.

You need…Reach forWhy
A single object (config, options, return value)Object literalShortest, clearest, no boilerplate
Many similar objects, plus private stateFactory functionClosures give privacy; no new pitfalls
Many similar objects, modern codeES6 classReadable, shared methods, easy inheritance
Direct prototype control / delegationObject.create()Assigns the prototype explicitly
To read or maintain legacy codeConstructor functionThe pre-2015 blueprint pattern

✅ Best practices

  • Do prefer object literals for one-offs and classes for reusable blueprints.
  • Do use method shorthand (greet() {}) over greet: function () {}.
  • Don't define shared methods inside a constructor's body — put them on the prototype or in the class.
  • Don't forget new with a constructor function; without it, this leaks to the global object.

Hands-on Exercise

🏋️ Build a Tiny Library System

Objective: Model books and a library that manages them, choosing the creation pattern that fits.

Instructions:

  1. Create a Book class with title, author, and an isBorrowed flag (default false).
  2. Give Book two methods: borrow() and returnBook() that flip the flag and log what happened.
  3. Create a library object literal with a books array and an addBook(book) method.
  4. Add two books, borrow one, and log the state of the collection.
💡 Hint

A class fits Book because you will make many of them. A literal fits library because there is only one. Inside borrow(), guard against borrowing an already-borrowed book before flipping this.isBorrowed.

✅ Sample solution
class Book {
  constructor(title, author) {
    this.title = title;
    this.author = author;
    this.isBorrowed = false;
  }

  borrow() {
    if (this.isBorrowed) {
      console.log(`"${this.title}" is already out.`);
      return;
    }
    this.isBorrowed = true;
    console.log(`Borrowed "${this.title}".`);
  }

  returnBook() {
    this.isBorrowed = false;
    console.log(`Returned "${this.title}".`);
  }
}

const library = {
  books: [],
  addBook(book) {
    this.books.push(book);
    console.log(`Added "${book.title}" to the library.`);
  },
};

const b1 = new Book('Eloquent JavaScript', 'Marijn Haverbeke');
const b2 = new Book('You Don’t Know JS', 'Kyle Simpson');

library.addBook(b1);
library.addBook(b2);
b1.borrow();

console.log(library.books.map((b) => `${b.title} — ${b.isBorrowed ? 'out' : 'available'}`));
// [ 'Eloquent JavaScript — out', 'You Don’t Know JS — available' ]

🎯 Quick Quiz

Question 1: Which creation pattern is the best fit for a single configuration object you will never duplicate?

Question 2: When you call a constructor function with new, what is this bound to?

Question 3: Why prefer putting shared methods on a prototype (or in a class body) rather than inside the constructor?

Summary & Quiz

🎉 Key Takeaways

  • Objects bundle properties (data) and methods (behavior) as key–value pairs.
  • Object literals are the go-to for one-off objects; classes are the modern blueprint for many instances.
  • Factory functions return objects and can hide private state via closures; constructor functions are the pre-ES6 blueprint that new drives.
  • new creates an empty object, binds this to it, links it to the prototype, and returns it.
  • Use dot notation for known names and bracket notation for dynamic or unusual names.

📚 Further Reading

🚀 What's Next?

Now that you can create objects six ways, the next lesson zooms into what lives inside them — how properties and methods really work, and the famously tricky this keyword that ties a method back to its object.

🎉 Well done!

You now own the full object-creation toolbox. Let's put those objects to work.