CSCI 4513
Week 3, Lecture 6
JavaScript doesn't have classes like Java or Ruby. ES6 introduced class syntax - a new way to write object constructors and prototypes that looks like classical OOP but uses the same prototypal inheritance under the hood.
Regular properties we've used so far:
let obj = {
name: "John",
age: 30
};
Data properties store values directly. Access them with obj.name or obj["age"].
Functions that get/set values:
let obj = {
get prop() { },
set prop(val) { }
};
Accessor properties run code when accessed. Look like properties, behave like functions!
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
}
};
alert(user.fullName); // John Smith
// Looks like a property, works like a function!
user.fullName, not user.fullName()name and surname dynamicallyuser.fullName normally (not as a function), but the getter runs behind the scenes.
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
};
// Setter is executed
user.fullName = "Alice Cooper";
alert(user.name); // Alice
alert(user.surname); // Cooper
[a, b] = str.split(" ") splits string into partsNow we have a "virtual" property that's both readable and writable!
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // Name is too short...
_name signals "private" (by convention only)returnname, internal storage is _name_ are internal and shouldn't be accessed directly.
// Old implementation
function User(name, age) {
this.name = name;
this.age = age;
}
// New implementation - switched to birthday
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// age is now calculated, but old code still works!
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1992, 6, 1));
alert(john.age); // Works! Calculated on the fly
age is calculated from birthday, always currentjohn.age still works!class MyClass {
// class methods
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
}
// Create new object with all methods
const instance = new MyClass();
newNote: No commas between class methods! (Unlike object literals)
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Usage:
let user = new User("John");
user.sayHi(); // John
new User(...)What happens when new User("John") is called:
this.name = "John"class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// Proof: User is a function!
alert(typeof User); // function
// More precisely, it's the constructor method
alert(User === User.prototype.constructor); // true
// Methods are in User.prototype
alert(User.prototype.sayHi); // the code of sayHi
// Two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype));
// constructor, sayHi
class creates a functionUser.prototypeclass User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
let user = new User("John");
user.sayHi();
function User(name) {
this.name = name;
}
User.prototype.sayHi =
function() {
alert(this.name);
};
let user = new User("John");
user.sayHi();
Both create the same result! Class syntax is cleaner and keeps methods together. Constructor functions require separate .prototype assignments. Same prototype inheritance underneath.
newfor..inclass User {
constructor() {}
}
User(); // Error: Cannot be invoked without 'new'
Unlike constructor functions, classes enforce the new keyword. This prevents accidental bugs where forgetting new would pollute the global scope.
// Anonymous class expression
let User = class {
sayHi() {
alert("Hello");
}
};
// Named class expression
let User = class MyClass {
sayHi() {
alert(MyClass); // Name visible only inside class
}
};
new User().sayHi(); // works
alert(MyClass); // Error: MyClass not visible outside
function makeClass(phrase) {
// Declare and return a class
return class {
sayHi() {
alert(phrase);
}
};
}
// Create a new class dynamically
let User = makeClass("Hello");
new User().sayHi(); // Hello
phrase from when it was createdclass User {
constructor(name) {
// Invokes the setter
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name is too short.
this.name = name triggers the setter!_name stores the actual valuenameclass User {
// Computed method name using brackets
['say' + 'Hi']() {
alert("Hello");
}
}
new User().sayHi(); // Hello
[expression] evaluates to method name'say' + 'Hi' becomes 'sayHi'Just like object literals, classes support computed property names with [...]
class User {
name = "John"; // Class field
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
β οΈ Important: Class fields are set on individual objects, NOT on User.prototype
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
// Field is on the instance, not the prototype!
name exists on user objectUser.prototype.name is undefinedClass fields can use complex expressions and function calls:
class User {
name = prompt("Name, please?", "John");
}
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
// 'this' is lost!
thisthis becomes global/undefined in callbackaddEventListener, map, forEachβ οΈ Problem: When passed to setTimeout, the method loses its this context.
class Button {
constructor(value) {
this.value = value;
}
// Arrow function field - binds 'this'!
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
// Works! 'this' is preserved
this from surrounding scopeclick = () => {} creates bound methodthis bound correctly.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
alert(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
speak() {
alert(`${this.name} barks.`);
}
}
let dog = new Dog("Rex");
dog.speak(); // Rex barks.
speak() replaces Animal'sthis.name works because it's inheritedclass Animal {
constructor(name) {
this.name = name;
}
speak() {
alert(`${this.name} makes a sound.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
super.speak(); // Call parent method
alert(`${this.name} is a ${this.breed}.`);
}
}
this only AFTER calling super()class BankAccount {
#balance = 0; // Private field
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
let account = new BankAccount();
account.deposit(100);
alert(account.getBalance()); // 100
alert(account.#balance); // Error: Private field!
#balance in class body# is enforced, _ is just convention#
class User {
#password;
constructor(username, password) {
this.username = username;
this.#password = this.#hashPassword(password);
}
// Private method
#hashPassword(password) {
return password.split('').reverse().join('');
}
checkPassword(password) {
return this.#hashPassword(password) === this.#password;
}
}
let user = new User("john", "secret");
user.#hashPassword("test"); // Error: Private method!
#hashPassword is implementation detailcheckPassword is the safe interfaceclass User {
static compare(a, b) {
return a.name > b.name ? 1 : -1;
}
constructor(name) {
this.name = name;
}
}
let users = [
new User("John"),
new User("Alice")
];
users.sort(User.compare); // Call static method
alert(users[0].name); // Alice
User.compare(), not user.compare()thisthis inside static methods refers to the classStatic methods are called on the class, not instances!
class Article {
static publisher = "Daily News";
static count = 0;
constructor(title) {
this.title = title;
Article.count++;
}
static getPublisher() {
return Article.publisher;
}
}
let article1 = new Article("News 1");
let article2 = new Article("News 2");
alert(Article.count); // 2
alert(Article.getPublisher()); // Daily News
alert(article1.publisher); // undefined (not on instances!)
count shared by all instancespublisher is configuration dataArticle.count, not article1.countthisclass MyClass {
prop = value; // Property (field)
constructor(...) { // Constructor
// ...
}
method(...) {} // Method
get something(...) {} // Getter
set something(...) {} // Setter
#privateProp = value; // Private field
#privateMethod() {} // Private method
static staticProp = value; // Static property
static staticMethod() {} // Static method
[Symbol.iterator]() {} // Computed name
}
Click each card to reveal the answer!
What differences are there between object constructors and classes?
Answer: Classes are syntactic sugar over constructors. Classes use class keyword and constructor() method. Constructors use function keyword. Both create objects via prototypes, but classes provide cleaner syntax and enforce new keyword usage.
What are getters and setters?
Answer: Methods that get or set property values using get and set keywords. They look like properties when used (user.fullName) but run logic behind the scenes. Great for validation, computed properties, and maintaining clean interfaces.
How is inheritance used with classes?
Answer: Use extends keyword to inherit from parent class. Use super() in constructor to call parent constructor. Use super.methodName() to call parent methods. Child classes inherit all parent properties and methods.
What are private class fields and methods?
Answer: Fields/methods prefixed with # that are only accessible inside the class. They provide true encapsulationβcannot be accessed or modified from outside. Example: #privateField or #privateMethod().
What are static properties and methods?
Answer: Belong to the class itself, not instances. Called on the class name (MyClass.staticMethod()), not on instances. Used for utility functions, factory methods, or shared data across all instances.
π Next Class: ES6 Modules, npm, and Webpack