Day 15. Classes

On Day 15, I learned about how to create a class and how to create objects from the class. I think it’s really important to understand the concept of OOP(Object Oriented Programming) to use the class well in the code.

header image TIL

Exercises

Links to read

Classes

JavaScript is an object oriented programming language. Everything in JavaScript is an object, with its properties and methods. Class is created to create an object. A class is like an object template, or a blueprint for creating objects.

To create an object(to make an instance of the class), I instantiate a class. An instance is an object, containing data and behavior described by the class. The class defines attributes and the behavior of the object, while the object represents the class.

Once a class is created, I can create object from it whenever I want. Creating an object from a class is called class instantiation. Class instantiation is done by using constructor, and the keyword new is used to signal that a constructor is being called.

In the object section, I learned how to create an object literal. An object literal is a list of key-value pairs, enclosed in curly braces({}). Object literal is a singleton, which means I have to write it again when I want similar object. However, the class allows to create many objects. It helps to reduce the amount of code and repetition of code.

1. Defining a Class

To define a class in JavaScript, I need to use the keyword class, the name of a class in CamelCase and block code wrapped in curly braces.

class ClassName {
  // code goes here
}
class Person {
  // code goes here
}

On the upper code, the class ā€˜Person’ doesn’t have any objects inside.

2. Class Instantiation

Class instantiaion means creating an object from a class. I need to use the keyword new, and I call the name of the class after the word new.

class Person {
  // code goes here
}
const person = new Person();

console.log(person);
// Person {}

Since the class doesn’t have any properties, the object is also empty.

3. Class Constructor

The constructor is a built-in function which allows to create a template for the object. The function starts with a keyword constructor, followed by parentheses. The properties of the objects are passed as parameter inside the parentheses. It creates a class and initialize an object instance of that class.

this keyword is used to attach the constructor parameters with the class. this refers to the class itself.

class Person {
  constructor(firstName, lastName) {
    console.log(this);  // PersonĀ {}
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
const person = new Person();

console.log(person);
// PersonĀ {firstName: undefined, lastName: undefined}

The firstName and lastName properties are attatched to the Person class, using this keyword.

On the upper code, all the keys of the object are undefined. Whenever I instantiate, I should pass the value of the properties.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}
const person1 = new Person('Ramona', 'Doe');

console.log(person1);
// PersonĀ {firstName: 'Ramona', lastName: 'Doe'}


Once I created a class, I can create many objects using the class.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

const person1 = new Person('Ramona', 'Doe');
const person2 = new Person('Paul', 'Doe');
const person3 = new Person('Violet', 'Doe');

console.log(person1);
console.log(person2);
console.log(person3);
/*
  PersonĀ {firstName: 'Ramona', lastName: 'Doe'}
  PersonĀ {firstName: 'Paul', lastName: 'Doe'}
  PersonĀ {firstName: 'Violet', lastName: 'Doe'}
*/


I can add more properites to the class.

class Person {
  constructor(firstName, lastName, age, country, city) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
    this.city = city;
  }
}
const person = new Person('Ramona', 'Doe', '23', 'Canada', 'Vancouver');

console.log(person);
// PersonĀ {firstName: 'Ramona', lastName: 'Doe', age: '23', country: 'Canada', city: 'Vancouver'}


4. Default values with constructor

The constructor function properties may have a default value like other regular functions.

class Person {
  constructor(
    firstName = 'Jane',
    lastName = 'Doe', 
    age = 30,
    country = 'US',
    city = 'Washington'
  ) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
    this.city = city;
  }
}
// it takes default values
const defaultUser = new Person();
const realUser = new Person('Ramona', 'Doe', 23, 'Canada', 'Vancouver');

console.log(defaultUser);
console.log(realUser);
/*
  PersonĀ {firstName: 'Jane', lastName: 'Doe', age: 30, country: 'US', city: 'Washington'}
  PersonĀ {firstName: 'Ramona', lastName: 'Doe', age: 23, country: 'Canada', city: 'Vancouver'}
*/


5. Class methods

In a class, I can create class methods. Methods are JavaScript functions inside the class.

class Person {
  constructor(firstName, lastName, age, country) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
  }
  getFullName() {
    const fullName = this.firstName + ' ' + this.lastName;
    return fullName;
  }
}
const person1 = new Person('Jamie', 'Doe', 20, 'Korea');
const person2 = new Person('Ramona', 'Doe', 23, 'Canada');

console.log(person1.getFullName());
console.log(person2.getFullName());
/*
  Jamie Doe
  Ramona Doe
*/


6. Properties with initial value

When creating class, I can set initial value for some properties. For example, the starting score in a game can be set to zero. Or the skills could be initially set and acquired afterwards.

class Person {
  constructor(userName, age, country, score, skills) {
    this.userName = userName;
    this.age = age;
    this.country = country;
    this.score = 0;
    this.skills = [];
  }
}
const person1 = new Person('Allison', 31, 'Canada');

// accessing properties directly from the object 
console.log(person1.score);
console.log(person1.skills);
/*
  0
  []
*/



Methods could be a regular method, getter, or setter.

7. getter

The getter method allows to access the value from the object. I need to use a keyword get and the function after it. Instead of accessing properties directly from the object, I can use getter to get the value.

class Person {
  constructor(firstName, lastName, age, country) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
  }
  getFullName() {
    const fullName = this.firstName + ' ' + this.lastName;
    return fullName;
  }
  get getScore() {
    return this.score;
  }
  get getSkills() {
    return this.skills;
  }
}
const person1 = new Person('Ramona', 'Doe', 23, 'Canada');

// using getter to access properites from the objecta
console.log(person1.getScore);
console.log(person1.getSkills);
/*
  0
  []
*/

I don’t need parentheses to call a getter method.


8. setter

The setter method allows to modify the value of certain properties. I need to use a keyword set and the function after it.

class Person {
  constructor(name, age, score, skills) {
    this.name = name;
    this.age = age;
    this.score = 0;
    this.skills = [];
  }
  get getScore() {
    return this.score;
  }
  get getSkills() {
    return this.skills;
  }
  set setScore(score) {
    this.score += score;
  }
  set setSkill(skill) {
    this.skills.push(skill);
  }
}
const person1 = new Person('Allsion', 31);
const person2 = new Person('Vanya', 29);

person1.setScore = 100;
person1.setSkill = 'Rumor';
person2.setScore = 700;
person2.setSkill = 'White Violin';

console.log(person1);
console.log(person2);
/*
  PersonĀ {name: 'Allsion', age: 31, score: 100, skills: ['Rumor']}
  PersonĀ {name: 'Vanya', age: 29, score: 700, skills: ['White Violin']}
*/
class Person {
  constructor(firstName, lastName, age, country, score, skills) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
    this.score = 0;
    this.skills = [];
  }
  getFullName() {
    const fullName = this.firstName + ' ' + this.lastName;
    return fullName;
  }
  get getScore() {
    return this.score;
  }
  get getSkills() {
    return this.skills;
  }
  set setScore(score) {
    this.score += score;
  }
  set setSkill(skill) {
    this.skills.push(skill);
  }
  getPersonInfo() {
    let fullName = this.getFullName();
    // add all the skills by comma and 'and' at the last
    // only operated if at least one skill exists
    let skills = this.skills.length > 0 && 
    this.skills.slice(0, this.skills.length - 1).join(', ') 
    + ` and ${this.skills[this.skills.length - 1]}`;
    // using ternary operator
    let formattedSkills = skills ? `She knows ${skills}.` : ''
    let info = `${fullName} is ${this.age}. She lives in ${this.country}. ${formattedSkills}`;
    return info;
  }
}
const person1 = new Person('Ramona', 'Doe', 23, 'Canada');

person1.setScore = 100;
person1.setSkill = 'HTML';
person1.setSkill = 'CSS';
person1.setSkill = 'JavaScript';

// need parentheses to call a regular method
console.log(person1.getPersonInfo());
// Ramona Doe is 23. She lives in Canada. She knows HTML, CSS and JavaScript.

Don’t forget to write return keyword to actually get the result from a function.


9. Static method

The static keyword defines a static method for a class. Static methods are not called on instances of the class. Instead, they are called on the class itself. These are often utility functions that are used to create or clone objects. Date.now() is an example of a static method. The now method is called directly from the class.

class Person {
  constructor(firstName, lastName, age, country) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
    this.score = 0;
    this.skills = [];
  }
  getFullName() {
    const fullName = this.firstName + ' ' + this.lastName;
    return fullName;
  }
  get getScore() {
    return this.score;
  }
  get getSkill() {
    return this.skills;
  }
  set setScore(score) {
    this.score += score;
  }
  getPersonInfo() {
    let fullName = this.getFullName(); 
    let skills = this.skills.length > 0 && 
    this.skills.slice(0, this.skills.length - 1).join(', ') 
    + ` and ${this.skills[this.skills.length - 1]}`;
    let formattedSkills = skills ? `She knows ${skills}.` : ''
    let info = `${fullName} is ${this.age}. She lives in ${this.country}. ${formattedSkills}`;
    return info;
  }
  static favoriteSkill() {
    const skills = ['HTML', 'CSS', 'JS', 'React', 'Python', 'Node'];
    const index = Math.floor(Math.random() * skills.length)
    return skills[index];
  }
  static showDateTime() {
    let now = new Date();
    let year = now.getFullYear();
    let month = now.getMonth() + 1;
    let date = now.getDate();
    let hours = now.getHours();
    let minutes = now.getMinutes();
    if (hours < 10) {
      hours = '0' + hours;
    }
    if (minutes < 10) {
      minutes = '0' + minutes;
    }
    let dateMonthYear = `${date}/${month}/${year}`;
    let time = `${hours}:${minutes}`;
    let fullTime = dateMonthYear + ' ' + time;
    return fullTime;
  }
}

console.log(Person.favoriteSkill());  // CSS
console.log(Person.showDateTime());
// 14/5/2022 22:19


10. Inheritance

Using inheritance, I can access all the properties and the methods of the parent class. If the objects are sharing some common properties, I can define a new class that has all the common properties. Then I can make other classes derive from the common class, adding extra properties. It reduces the repetition of code.

class ChildClassName extends ParentClassName {
  // code goes here
}
class Person {
  constructor(firstName, lastName, age, country) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
  }
  getFullName() {
    const fullName = this.firstName + ' ' + this.lastName;
    return fullName;
  }
}
class Student extends Person {
  greet() {
    console.log("Hi, I'm a person and a student.");
  }
}
const stud1 = new Student('Ramona', 'Doe', 23, 'Canada');

console.log(stud1);
console.log(stud1.greet());
console.log(stud1.getFullName());
/*
  StudentĀ {firstName: 'Ramona', lastName: 'Doe', age: 23, country: 'Canada'}
  Hi, I'm a person and a student.
  Ramona Doe
*/


11. Overriding methods

In the examples above, I managed to access all the methods in the Person class, and used it in the Student child class. I can customize the parent methods, and I can add additional properties to a child class. If I want to customize the methods, and if I want to add extra properties, I need to use the constructor function in the child class too.

Inside the constructor function, I can call the super() function to access all the properties from the parent class. In the code below, the Person class didn’t have gender property, but the child class Student can have that property. If the same method name is used in the child class, the parent method will be overridden.

class Person {
  constructor(firstName, lastName, age, country) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.country = country;
  }
  getFullName() {
    const fullName = this.firstName + ' ' + this.lastName;
    return fullName;
  }
  getPersonInfo() {
    let fullName = this.getFullName();
    let info = `${fullName} is ${this.age}. She lives in ${this.country}.`;
    return info;
  }
}

class Student extends Person {
  constructor(firstName, lastName, age, country, gender) {
    super(firstName, lastName, age, country);
    this.gender = gender;
  }
  greet() {
    console.log("Hi, I'm a person and a student.");
  }
  // it will replace the function with the same name in parent class
  getPersonInfo() {
    let fullName = this.getFullName();
    let pronoun = this.gender == 'female' ? 'She' : 'He';
    let info = `${fullName} is ${this.age}. ${pronoun} lives in ${this.country}.`;
    return info;
  }
}
const stud1 = new Student('Ramona', 'Doe', 23, 'Canada', 'female');

console.log(stud1.getPersonInfo());
// Ramona Doe is 23. She lives in Canada.

The function getPersonInfo() was overridden by the function at the child class.



Day 15. Classes_Exercise

šŸ‘ŸLevel 1.

1. Create an Animal class. The class will have name, age, color, legs properties and create different methods.
class Animal {
  constructor(name, age, color, legs) {
    this.name = name;
    this.age = age;
    this.color = color;
    this.legs = legs;
  }
  getAnimalInfo() {
    let info = `Meet ${this.name}! It is ${this.age} years old, ${this.color}, and has ${this.legs} legs.`;
    return info;
  }
}
const bird = new Animal('Jay', 3, 'blue', 2);

console.log(bird.getAnimalInfo());
// Meet Jay! It is 3 years old, blue, and has 2 legs.


2. Create a Dog and Cat child class from the Animal Class.
class Dog extends Animal{
  introduce() {
    console.log("Hi, I'm a dog!");
  }
}
const dog1 = new Dog('Bolt', 5, 'brown', 4);

console.log(dog1);
console.log(dog1.introduce());
/*
  DogĀ {name: 'Bolt', age: 5, color: 'brown', legs: 4}
  Hi, I'm a dog!
*/
class Cat extends Animal{
  introduce() {
    console.log("What\'s up, I'm a cat.");
  }
}
const cat1 = new Dog('Lucy', 6, 'black', 4);

console.log(cat1);
console.log(cat1.introduce());
/*
  CatĀ {name: 'Lucy', age: 6, color: 'black', legs: 4}
  What's up, I'm a cat.
*/


šŸ‘ŸLevel 2.

1. Override the method you create in Animal class.
class Cat extends Animal {
  constructor(name, age, color, legs, gender, character) {
    super(name, age, color, legs)
    this.gender = gender;
    this.character = character;
  }
  getAnimalInfo() {
    let pronoun = this.gender == 'female' ? 'She' : 'He';
    let info = `Meet ${this.name}! ${pronoun} is ${this.age} years old, ${this.character}, ${this.color}, and has ${this.legs} legs.`
    return info;
  }
}
const cat1 = new Cat('Blanc', 3, 'white', 4, 'female', 'curious');

console.log(cat1);
console.log(cat1.getAnimalInfo());
/*
  CatĀ {name: 'Blanc', age: 3, color: 'white', legs: 4, gender: 'female',Ā character: curious}
  Meet Blanc! She is 3 years old, curious, white, and has 4 legs.
*/

To add extra properties to a child class, I have to first write constructor and call super() to bring the function from the parent class. Also, I should pass all the property names to a constructor parameter.

šŸ‘ŸLevel 3.

1. Let’s try to develop a program which calculate measure of central tendency of a sample(mean, median, mode) and measure of variability(range, variance, standard deviation). In addition to those measures find the min, max, count, percentile, and frequency distribution of the sample.
You can create a class called Statistics and create all the functions which do statistical calculations as method for the Statistics class.


2. Create a class called PersonAccount. It has firstname, lastname, incomes, expenses properties and it has totalIncome, totalExpense, accountInfo,addIncome, addExpense and accountBalance methods. Incomes is a set of incomes and its description and expenses is also a set of expenses and its description.


class PersonAccount {
  constructor(firstName, lastName, incomes, expenses) {
    this.firstName = firstName;
    this.lastName  = lastName;
    this.incomes = incomes;
    this.expenses = expenses;
  }
  totalIncome() {
    const incomeValues = Object.values(this.incomes);
    const totalIncome = incomeValues.reduce((a, b) => a + b);
    return totalIncome;
  }
  totalExpense() {
    let expenseValues = Object.values(this.expenses);
    const totalExpense = expenseValues.reduce((a, b) => a + b);
    return totalExpense;
  }
  accountInfo() {
    let incomeKeys = Object.keys(this.incomes);
    let formattedIncomes = `${incomeKeys.slice(0, -1).join(', ')} and ${incomeKeys.slice(-1)}`;
    let expenseKeys = Object.keys(this.expenses);
    let formattedExpenses = `${expenseKeys.slice(0, -1).join(', ')} and ${expenseKeys.slice(-1)}`

    let info = `This is ${this.firstName} ${this.lastName}'s account.\nThe user has incomes from ${formattedIncomes}.\nThe total income is ${this.totalIncome()}.\nThe user has expenses from ${formattedExpenses}.\nThe total expense is ${this.totalExpense()}.`
    return info;
  }
  addIncome(source, value) {
    this.incomes[`${source}`] = value;
    return this.incomes;
  }
  addExpense(source, value) {
    this.expenses[`${source}`] = value;
    return this.expenses;
  }
}
const account1 = new PersonAccount('Ramona', 'Doe', {'salary' :  500, 'online course' : 700, 'part time' : 150}, {'bills' : 150, 'groceries' : 250});

console.log(account1.accountInfo());
console.log(account1.addIncome('flea market', 50));
console.log(account1.addExpense('books', 30));
/* 
  This is Ramona Doe's account.
  The user has incomes from salary, online course and part time.
  The total income is 1350.
  The user has expenses from bills and groceries.
  The total expense is 400.

  {salary: 500, online course: 700, part time: 150, flea market: 50}

  {bills: 150, groceries: 250, books: 30}
*/


  • Mistakes I’ve been through

1) Defining methods, I should use this keyword to properly pass the value. If I don’t use the keyword, the code will give a reference error.

2) Defining addIncome() and addExpense(), I first wrote like below:

addIncome(source, value) {
    this.incomes['source'] = value;
    return this.incomes;
}

It would literally add ā€˜source’ as a key. To use the argument as a string, I should use string literals.


Leave a comment