JS Objects and Constructors

JavaScript Objects

Objects & Constructors

CSCI 4513

Week 2, Lecture 4

Today's Learning Objectives

Object Basics Refresher

// Object literal syntax
const myObject = {
  property: 'Value!',
  otherProperty: 77,
  "obnoxious property": function() {
    // do stuff!
  }
};

Dot Notation

myObject.property;
// 'Value!'

Cleaner, preferred when possible

Bracket Notation

myObject["obnoxious property"];
// [Function]

Required for strings with spaces, variables

Why Bracket Notation?

const variable = 'property';

// Dot notation looks for 'variable' literally
myObject.variable;  // undefined

// Bracket notation evaluates the variable
myObject[variable]; // 'Value!'
myObject['property']; // Same as above

Objects as a Design Pattern

❌ Without Objects

const playerOneName = "tim";
const playerTwoName = "jenn";
const playerOneMarker = "X";
const playerTwoMarker = "O";

console.log(playerOneName);
console.log(playerTwoName);

Hard to manage, not reusable

✅ With Objects

const playerOne = {
  name: "tim",
  marker: "X"
};

const playerTwo = {
  name: "jenn",
  marker: "O"
};

console.log(playerOne.name);
console.log(playerTwo.name);

Organized, scalable

Why Objects Are Better

// Can write generic functions
function printName(player) {
  console.log(player.name);
}

printName(playerOne);  // "tim"
printName(playerTwo);  // "jenn"

// Works with any player!
function gameOver(winningPlayer) {
  console.log("Congratulations!");
  console.log(winningPlayer.name + " is the winner!");
}

Objects let you write reusable code that works with any data structure.

Object Constructors

A constructor function is a blueprint for creating multiple objects of the same type.

// Constructor function (uppercase by convention)
function Player(name, marker) {
  this.name = name;
  this.marker = marker;
}

// Create instances with 'new' keyword
const player1 = new Player('steve', 'X');
const player2 = new Player('also steve', 'O');

console.log(player1.name);  // 'steve'
console.log(player2.marker); // 'O'

Adding Methods to Constructors

function Player(name, marker) {
  this.name = name;
  this.marker = marker;
  this.sayName = function() {
    console.log(this.name);
  };
}

const player1 = new Player('steve', 'X');
const player2 = new Player('also steve', 'O');

player1.sayName(); // logs 'steve'
player2.sayName(); // logs 'also steve'

Each player instance gets its own copy of the properties AND methods.

Safeguarding Constructors

⚠️ Problem: Constructors are just functions - they can be called without new!

function Player(name, marker) {
  // Guard against missing 'new' keyword
  if (!new.target) {
    throw Error("You must use 'new' to call the constructor");
  }

  this.name = name;
  this.marker = marker;
  this.sayName = function() {
    console.log(this.name);
  };
}

// This works
const player = new Player('steve', 'X');

// This throws an error
const oops = Player('steve', 'X'); // Error!

Exercise: Book Constructor

Your Task:

Create a Book constructor that takes title, author, pages, and read status. Add an info() method that returns a string.

// Expected usage:
const theHobbit = new Book(
  'The Hobbit',
  'J.R.R. Tolkien',
  295,
  false
);

console.log(theHobbit.info());
// "The Hobbit by J.R.R. Tolkien, 295 pages, not read yet"

Tip: Return the string from info(), don't console.log directly!

The Prototype

Three Key Facts:

  1. All objects in JavaScript have a [[Prototype]]
  2. The [[Prototype]] is another object
  3. Objects inherit from their [[Prototype]]

The prototype is how JavaScript implements inheritance. Objects can access properties and methods defined on their prototype.

Accessing an Object's Prototype

function Player(name, marker) {
  this.name = name;
  this.marker = marker;
}

const player1 = new Player('steve', 'X');
const player2 = new Player('also steve', 'O');

// Check the prototype
Object.getPrototypeOf(player1) === Player.prototype; // true
Object.getPrototypeOf(player2) === Player.prototype; // true

Defining Methods on the Prototype

function Player(name, marker) {
  this.name = name;
  this.marker = marker;
}

// Define method on prototype (shared by all instances)
Player.prototype.sayHello = function() {
  console.log("Hello, I'm a player!");
};

const player1 = new Player('steve', 'X');
const player2 = new Player('also steve', 'O');

player1.sayHello(); // "Hello, I'm a player!"
player2.sayHello(); // "Hello, I'm a player!"

Why? Saves memory! All instances share the same method instead of each having their own copy.

Common Confusion: [[Prototype]] vs .prototype

.prototype (on constructor functions)

Property of constructor functions. Determines what new instances' [[Prototype]] will be.

[[Prototype]] (internal property)

The actual prototype of an object instance. Access with Object.getPrototypeOf().

Player.prototype;  // Constructor's prototype property
Object.getPrototypeOf(player1);  // Instance's [[Prototype]]

Object.getPrototypeOf(player1) === Player.prototype; // true!

Prototypal Inheritance

// Where did this come from?
player1.valueOf(); // Object { name: "steve", marker: "X" }

// It's inherited from Object.prototype!
player1.hasOwnProperty('valueOf'); // false
Object.prototype.hasOwnProperty('valueOf'); // true

// Check the prototype chain
Object.getPrototypeOf(Player.prototype) === Object.prototype;
// true!

JavaScript searches up the prototype chain until it finds the property or reaches the end (null).

Knowledge Check: The Prototype Chain

Click each card to reveal the answer!

❓ Question 1

When you call player1.valueOf() but valueOf isn't on the player1 object itself, what happens?

❓ Question 2

Where does the prototype chain end?

❓ Question 3

If you add a method to Player.prototype, can all instances created with new Player() access it?

Setting Up Prototypal Inheritance

function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(`Hello, I'm ${this.name}!`);
};

function Player(name, marker) {
  this.name = name;
  this.marker = marker;
}

Player.prototype.getMarker = function() {
  console.log(`My marker is '${this.marker}'`);
};

// Make Player inherit from Person
Object.setPrototypeOf(Player.prototype, Person.prototype);

Inheritance in Action

// After setting up inheritance...
const player1 = new Player('steve', 'X');
const player2 = new Player('also steve', 'O');

// Methods from Person
player1.sayName();      // Hello, I'm steve!
player2.sayName();      // Hello, I'm also steve!

// Methods from Player
player1.getMarker();    // My marker is 'X'
player2.getMarker();    // My marker is 'O'

Player objects can access methods from both Player.prototype AND Person.prototype!

⚠️ Prototypal Inheritance Warnings

❌ DON'T DO THIS:

// Makes them the SAME object!
Player.prototype = Person.prototype;

// Changes affect BOTH!
Enemy.prototype = Person.prototype;

✅ DO THIS:

// Makes Player inherit from Person
Object.setPrototypeOf(Player.prototype, Person.prototype);

Set up prototypes BEFORE creating instances for best performance!

The 'this' Keyword

this refers to the object that is executing the current function.

function Player(name, marker) {
  this.name = name;        // 'this' = new object being created
  this.marker = marker;
}

Player.prototype.sayName = function() {
  console.log(this.name);  // 'this' = object calling the method
};

const player1 = new Player('steve', 'X');
player1.sayName();  // 'this' refers to player1

'this' in Different Contexts

const player = {
  name: 'steve',
  sayName: function() {
    console.log(this.name);
  }
};

player.sayName();  // 'steve' - 'this' is player

const fn = player.sayName;
fn();  // undefined - 'this' is global/window!

// Arrow functions don't have their own 'this'
const player2 = {
  name: 'jenn',
  sayName: () => {
    console.log(this.name);  // 'this' from outer scope!
  }
};

Pay attention to how functions are called! this behavior changes.

Knowledge Check: 'this'

Click each card to reveal the answer!

❓ Question 1

What does this refer to inside a constructor function when you call it with new?

❓ Question 2

If you call a method like obj.method(), what does this refer to inside the method?

❓ Question 3

What happens to this in an arrow function?

Project: Library

Your Assignment:

Build a small library app that stores and displays books using object constructors and prototypes.

Key Features:

Library Project Structure

const myLibrary = [];

function Book(title, author, pages, read) {
  this.id = crypto.randomUUID();  // Unique ID!
  this.title = title;
  this.author = author;
  this.pages = pages;
  this.read = read;
}

// Prototype method to toggle read status
Book.prototype.toggleRead = function() {
  this.read = !this.read;
};

function addBookToLibrary(title, author, pages, read) {
  const book = new Book(title, author, pages, read);
  myLibrary.push(book);
  displayBooks();
}

Library Project Tips

1. Separation of Concerns

Keep data (books array) separate from display logic. Don't manipulate DOM directly in the constructor.

2. Form Submission

form.addEventListener('submit', (e) => {
  e.preventDefault();  // Prevent default form submission!
  // Get form data and add book
});

3. Data Attributes

<button data-book-id="${book.id}">Remove</button>

Use data attributes to connect DOM elements to book objects.

Project Implementation Steps

  1. Set up Git repository with HTML/CSS/JS files
  2. Create Book constructor with unique IDs
  3. Create addBookToLibrary() function
  4. Write displayBooks() function to show all books
  5. Add "New Book" button and form (use <dialog> tag!)
  6. Implement event.preventDefault() on form submit
  7. Add remove button for each book
  8. Add toggle read status button
  9. Create Book.prototype.toggleRead() method

Library Project: No Storage Required

📝 Important Note:

You're NOT required to add localStorage or any persistent storage. It's okay if books disappear on page reload!

Focus on mastering:

Today's Takeaways

You Can Now:

📅 Next Class: Factory Functions and Module Pattern

📝 Homework: Library