Articles
- https://exploringjs.com/impatient-js/ch_proto-chains-classes.html 90% explanation about prototype chaining
- https://www.javascriptjanuary.com/blog/es6-classes 100% new keyword protection
- How To Use Classes in JavaScript — Tania Rascia 100% mage & hero instance of subclasses proto
- Javascript Classes — Under The Hood — Majid 100% class = mix of constructor function and prototypes, problems of constructor function & prototypes
- Class vs Factory function: exploring the way forward — Cristi Salcescu
- How ES6 classes really work and how to build your own — Robert Grosse 50%
- An Easy Guide To Understanding Classes In JavaScript 100% Developer & Person
- https://javascript.plainenglish.io/factories-are-still-better-than-classes-in-javascript-47f15071904e factory vs class
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.
Top comments (0)