14. Factories and Classes

Articles


In Javascript, there are two ways for creating objects; factories and classes.

Factories

Factory function is a function that returns object.

const RocketShipFactory = (c) => {
  const color = c;
  
  return {
    fly: () => console.log(`The ${color} rocketship has launched.`),
    land: () => console.log(`The ${color} rocketship has landed.`)
  } 
}

const spaceX = RocketShipFactory('black');
spaceX.fly();

Above snippet is an simple factory function for making spaceX object.

Closure

What thing that gets my interest is that factory pattern usually uses closure for data encapsulation. In above snippet, color variable is unaccessible in global scope but we can access it indirectly through fly or land method.

console.log(spaceX.color); // undefined

Classes are just 'syntactic sugar' of prototypal inheritance. Purpose of class is to set up the prototype chain between class.prototype and instances. Let's look at prototype first.

Prototype

In javascript, every objects are linked each other through something called 'prototype chain'.

Class is just template of prototype

One thing that is very important is that instances created by class links to class.prototye, not class itself.

class Person {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return 'Person named '+this.name;
  }
}

const Jane = new Person('jane');
console.log(jane.describe());

In above snippet, object Jane is instance of class Person. So, Jane is linked to Person.prototype with prototype chain, not class Person itself.

.__proto__ vs .prototype

Above chart has properties called __proto__ and prototype. What are they?

__proto__ property is pseudo-property for accessing the prototype of an object. So, Jane's __proto__ proerty points to the Person.prototype object.

prototype property points to the prototype of all instances of class. It means that Person class's prototype property points to the Person.prototype.

Additionally, object Person.prototype's constructor property points to the class itself.

All methods (except static) of class are stored in prototype.

Another truth that is important is that all methods except static methods declared inside class are stored in prototype.

Back to pevious example, we can see that describe method is actually stored inside the Person.prototype object. This is why we call that class is just template/syntatctic sugar of prototypal programming.
But, static methods are stored in class itself.


Classes

normal & static methods

Normal methods are inherited from classes to instances, but static methods are not inehrited and should use with class itself.


constructor function

Constructor function helps us to intialize with number of parameters which would be assigned as properties of this, which is class itself.


Getters/Setters

Getter function uses get keyword to get property value and Setter uses set keyword to set the property value. It can used for data encapsulation or for using method like property.

class Person {
  constructor(name){
    this._name = name;
  }
  get name() {
    return this._name;
  }
}

const Jane = new Person('jane');
console.log(Jane.name); // 'jane'
Jane.name = "alex";
console.log(Jane.name); // 'jane'

We can hide _name property and it would not be modified. Also, we can call name method like a property.


Subclasses

With subclasses, we can make class which is simiar or extended from the original classes.

class Person {
  constructor(name) {
    this.name = name;
  }

sayName() {
    console.log("My name is " + this.name);
  }
}

class Developer extends Person {
  constructor(name) {
    super(name);
    this.name = name;
  }

getBio() {
    super.sayName();
    console.log("I am a developer");
  }
}

let ReactGuy = new Developer("Lawrence Eagles");
ReactGuy.getBio(); // "My name is Lawrence Eagles"
	           // "I am a developer"

extend keyword

extend keyword makes subclasses.

super keyword

super keyword is used to access and call functions from the object's parent ( original class). In above snippet, super.sayName() calls sayName method of class Person. One thing to check is that this in sayName method refers to ReactGuy instance, not class itself.

If the super keyword is called inside the constructor, it calls the constructor function of parent class. For example, super(name) is called inside Developer constructor function. So, parameter variable name will be passed to the constructor function of Person class.

Prototype relation

When subcalss is created from original class, original class becomes the subcalss' prototype. For example, class Person is the Developer's prototype.

class Person {
  constructor(name) {
    this.name = name;
  }
  describe() {
    return `Person named ${this.name}`;
  }
  static logNames(persons) {
    for (const person of persons) {
      console.log(person.name);
    }
  }
}

class Employee extends Person {
  constructor(name, title) {
    super(name);
    this.title = title;
  }
  describe() {
    return super.describe() +
      ` (${this.title})`;
  }
}

const jane = new Employee('Jane', 'CTO');
assert.equal(
  jane.describe(),
  'Person named Jane (CTO)');

In above snippet, Employee is subclass of Person and Jane is instance of subclass Employee. Prototype chain looks as following chart.


Factory vs Classes

Both has some different advantages and disadvantages.

Data Encapsulation

First, sector is data encapsulation. In factory, we can control if we want data to be private or public by using closure. However, in classes, it's not that simple.

Classes; data encapsulation / getter & setter

As I mentioned, getter & setter is used for data encapsulation in classes. However, it's not systemically encapsulated. What it means by that is it's actually modifiable.

class Person {
  constructor(name){
    this._name = name;
  }
  get name() {
    return this._name;
  }
}

const Jane = new Person('jane');
console.log(Jane.name);
Jane._name = "alex";
console.log(Jane.name);

If we reassign the property _name, the value returned from name method changes. Although, in javascript, we conventionally promise not to modify variable with _ prefix. But it's possible.

Classes; data encapsulation / # prefix

# prefix is introduced recently for private class field.

class CoffeeMachine {
  #waterLimit = 200;

  #checkWater(value) {
    if (value < 0) throw new Error(".");
    if (value > this.#waterLimit) throw new Error(".");
  }

}

let coffeeMachine = new CoffeeMachine();

coffeeMachine.#checkWater(); // Error
coffeeMachine.#waterLimit = 1000; // Error

It looks nice, but one problem is that private methods in classes are also not accessible in subclasses.


this keyword

In class, this keyword goes through some scope confusion in certain situations. These situations are when this is used in nested function or in callback function.

The solution to this problem is arrow function.

class Car {
  constructor(maxSpeed){
    this.maxSpeed = maxSpeed;
  }
  drive = () => {
    console.log(`driving ${this.maxSpeed} mph!`)
  }
}

This works find for any circumstances.


Memory cost

Memory cost is problem to factory function. Unlike to class which only stores methods once in prototype, factory fuctions create copy of each methods on every instances they create. This could be problematic if the number of instances increase.

좋은 웹페이지 즐겨찾기