CSCI 4513
Week 2, Lecture 4
// Object literal syntax
const myObject = {
property: 'Value!',
otherProperty: 77,
"obnoxious property": function() {
// do stuff!
}
};
myObject.property;
// 'Value!'
Cleaner, preferred when possible
myObject["obnoxious property"];
// [Function]
Required for strings with spaces, variables
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
const playerOneName = "tim";
const playerTwoName = "jenn";
const playerOneMarker = "X";
const playerTwoMarker = "O";
console.log(playerOneName);
console.log(playerTwoName);
Hard to manage, not reusable
const playerOne = {
name: "tim",
marker: "X"
};
const playerTwo = {
name: "jenn",
marker: "O"
};
console.log(playerOne.name);
console.log(playerTwo.name);
Organized, scalable
// 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.
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'
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.
⚠️ 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!
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!
[[Prototype]][[Prototype]] is another object[[Prototype]]The prototype is how JavaScript implements inheritance. Objects can access properties and methods defined on their 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
Object.getPrototypeOf() to get an object's prototypePlayer.prototype.__proto__ (deprecated!)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.
.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!
// 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).
Click each card to reveal the answer!
When you call player1.valueOf() but valueOf isn't on the player1 object itself, what happens?
Answer: JavaScript looks up the prototype chain. It checks player1's [[Prototype]] (Player.prototype), then Player.prototype's [[Prototype]] (Object.prototype), until it finds valueOf or reaches null.
Where does the prototype chain end?
Answer: The chain ends at Object.prototype, whose prototype is null. Every object in JavaScript ultimately inherits from Object.prototype.
If you add a method to Player.prototype, can all instances created with new Player() access it?
Answer: Yes! All instances share the same prototype object. When you add a method to Player.prototype, every instance can access it through the prototype chain without storing a separate copy.
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);
// 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!
// Makes them the SAME object!
Player.prototype = Person.prototype;
// Changes affect BOTH!
Enemy.prototype = Person.prototype;
// Makes Player inherit from Person
Object.setPrototypeOf(Player.prototype, Person.prototype);
Set up prototypes BEFORE creating instances for best performance!
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
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.
Click each card to reveal the answer!
What does this refer to inside a constructor function when you call it with new?
Answer: this refers to the new object being created. The new keyword creates a new empty object and sets this to point to it.
If you call a method like obj.method(), what does this refer to inside the method?
Answer: this refers to the object before the dot (obj). The object calling the method becomes this.
What happens to this in an arrow function?
Answer: Arrow functions don't have their own this. They inherit this from the surrounding (lexical) scope where they were defined.
Build a small library app that stores and displays books using object constructors and prototypes.
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();
}
Keep data (books array) separate from display logic. Don't manipulate DOM directly in the constructor.
form.addEventListener('submit', (e) => {
e.preventDefault(); // Prevent default form submission!
// Get form data and add book
});
<button data-book-id="${book.id}">Remove</button>
Use data attributes to connect DOM elements to book objects.
addBookToLibrary() functiondisplayBooks() function to show all booksevent.preventDefault() on form submitBook.prototype.toggleRead() methodYou're NOT required to add localStorage or any persistent storage. It's okay if books disappear on page reload!
Focus on mastering:
📅 Next Class: Factory Functions and Module Pattern
📝 Homework: Library