# JavaScript common Patterns
What is JavaScript pattern:
Can be treated as a general solution for resolving your current faced issue, more like a template or re-usable solution for resolving abit more complex project structure issues .. (2021 understanding)
1). JavaScript Singleton Pattern
// A story: I want to display each of function files related log messages,
// so I create one singleton object as the ONLY instance,
// and I can call its instance function directly to KEEP (KEEP is similar like always adding) logging new messages !!
// Attention: the ONLY instance were created inside class and instantiate within its own class and the instance has finally been exported !!!
// Outside functions call directly use this instance and its methods !!
// Code example:
class Messager {
constructor() {
if (!Messager.instance) {
this.messages = [];
Messager.instance = this;
}
return Messager.instance;
}
show(message) {
this.messages.push(message);
console.log(`Message content: ${message}`);
}
counter() {
console.log(`This is ${this.messages.length} message ..`);
}
}
const messager = new Messager();
Object.freeze(messager); // keep logger object unchanged
export default messager;
// Please create new file: singleton-pattern-consumer-A
import messager from "./singleton-pattern-example";
export default function singletonConsumerA() {
messager.show("Red book is helpful.");
messager.counter();
}
// Please create new file: singleton-pattern-consumer-B
import messager from "./singleton-pattern-example";
export default function singletonConsumerB() {
messager.show("小红书不错。");
messager.counter();
}
// imports
import A from "./singleton-pattern-consumer-A";
import B from "./singleton-pattern-consumer-B";
A();
B();
2). JavScript Factory Pattern
// What is pattern in programming languages?
// A pattern is a general solution or mindset can be used for resolving your faced issues.
// JavaScript Factory Pattern:
// One word: it can be used for creating different types of objects, each of these object can be treated as a unique instance.
// A story: we have multiple terminal machines which wants to connect with POS system.
// And each of them will connect with a terminal connection library instance in order for tracking each terminal's specific behaviour.
// This process is called as pair a terminal. In order to achieve this functionality, we need to create library instance as an object to handle each of the pair connections.
// Code example:
function TerminalA(name, type) {
this.name = name;
this.type = type;
}
function TerminalB(name, type) {
this.name = name;
this.type = type;
}
function PairConnectionLibrary() {
this.create = (name, type) => {
switch (type) {
case "TypeA":
return new TerminalA(name, type);
case "TypeB":
return new TerminalB(name, type);
default:
return `Sorry ${name}, this type ${type} not support at moment ..`;
}
};
}
function readFactoryInstance() {
console.log(
`Thank you for adding ${this.name} to current terminal list with the type of ${this.type}`
);
}
const factoryInstance = new PairConnectionLibrary();
const terminals = [];
terminals.push(factoryInstance.create("T1", "TypeA"));
terminals.push(factoryInstance.create("T2", "TypeB"));
terminals.forEach((terminal) => {
readFactoryInstance.call(terminal);
});
3). JavaScript Constructor Pattern
// Normally, JavaScript object constructors are used for creating object based on the specific needs
In JavaScript, 2 common ways to create an new object:
let newObject = {}; // way 1
let newObject = new Object(); // way 2
In JavaScript, 4 ways to assign attribute into an object
// Dot Syntax
let newObject;
newObject.newProp = "assigned newProp into object newObject";
// Square Bracket Syntax
let newObject;
newObject["key"] = "value";
// Object.defineProperty
let newObject = {};
Object.defineProperty(newObject, "newKey", {
value: "new value",
});
// Object.defineProperties
let newObject = {};
Object.defineProperties(newObject, {
key1: {
value: "value 1",
},
key2: {
value: "value 2",
},
});
Last example, constructor with prototypes
function Property(type, year, owner) {
this.type = type;
this.year = year;
this.owner = owner;
}
Property.prototype.word = function() {
return `${this.owner} has own a year ${this.year} ${this.type}`;
};
let exampleOne = new Property("apartment", "2018", "MrX");
let exampleTwo = new Property("house", "2008", "MrsY");
console.log(exampleOne.word());
console.log(exampleTwo.word());
4). JavaScript Module Pattern
// It is a common pattern which was used for wrapping a set of variables or objects in a single scope
// The reason why we use module pattern:
// 1). maintainability
// 2). reusability
function EmployeeDetails() {
var name = "MrX";
var salary = 1000000;
var totalSalary = function(bonus, expense = 0) {
return salary + bonus + expense;
};
return {
name,
totalSalary,
};
}
var employeeDetails = new EmployeeDetails();
const getSalary = employeeDetails.totalSalary(20000, 10000);
console.log(getSalary); // 1030000
console.log(salary); // ReferenceError: salary is not defined
// This Example is a typical example when we want to keep some variable privately within that specific scope, we can use module pattern to only make the variables we want to public !!!
// Also keep employee details in one module, can be re-used for other functionalities or other classes/modules later
5). JavaScript Prototype Pattern
var greetings = {
word: "Hello",
phrase: function() {
console.log("What's up");
},
sentence: function(name) {
console.log(`Hi ${name}, how are you doing?`);
},
};
const myGreetings = Object.create(greetings);
console.log(myGreetings.sentence("MrRight"));
Another example:
const zombie = {
eatBrains() {
return "yum 🧠 ..";
},
};
const obj = Object.create(zombie, { name: { value: "object" } });
// console.log(obj.eatBrains());
// console.log(obj.__proto__.eatBrains());
// console.log(obj.__proto__);
console.log(Object.getPrototypeOf(obj)); // the modern practice
// In JS: prototype refers to constructor
Array.prototype.bad = function() {
console.log("prototype is a constructor ...");
console.log(
"which means we can extend a class with an additional function we want to !!!"
);
};
Array.prototype.bad(); // this is bad code in JS
// better just define as a function !!
6). JavaScript Observer Pattern
// subject & observer are the 2 most essential parties for this pattern
// observer observes published/updated subject constantly
class Observer {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
broadcast(data) {
this.observers.forEach((subscriber) => subscriber(data));
}
}
const getTextCountResult = (text) =>
text ? text.trim().split(/s+/).length : 0;
const wordCountDOM = document.createElement("p");
wordCountDOM.innerHTML = 'Word Count: <strong id="counterValue">0</strong>';
document.body.appendChild(wordCountDOM);
const inputObserver = new Observer();
inputObserver.subscribe((text) => {
const counterValueDOM = document.getElementById("counterValue");
counterValueDOM.textContent = getTextCountResult(text);
});
const inputDOM = document.getElementById("words");
inputDOM.addEventListener("keyup", () =>
inputObserver.broadcast(inputDOM.value)
);
// HTML part
<textarea id="words" placeholder="Please type word here .."></textarea>;
7). Strategy pattern (Purpose: try to make code more reusable)
Example: try to make if statement more reusable
// before
function getExperience(level, experience) {
const levelUpperCase = level.toUpperCase();
if (levelUpperCase === "S") {
return experience * 10;
}
if (levelUpperCase === "A") {
return experience * 5;
}
if (levelUpperCase === "B") {
return experience * 2;
}
return experience;
}
getExperience("A", 10); // 50
// after
const strategy = {
S: function(experience) {
return experience * 10;
},
A: function(experience) {
return experience * 5;
},
D: function(experience) {
return experience * 2;
},
};
function getExperienceByStrategy(strategy, level, experience) {
return level in strategy ? strategy[level](experience) : experience;
}
let s = getExperienceByStrategy(strategy, "S", 10);
let a = getExperienceByStrategy(strategy, "A", 10);
let d = getExperienceByStrategy(strategy, "D", 10);
console.log(s, a, d); // 23 100 50 20
8). Builder pattern
class HotDog {
constructor(
bun = "Unknown",
ketchup = false,
mustard = false,
kraut = false
) {
this.bun = bun;
this.ketchup = ketchup;
this.mustard = mustard;
this.kraut = kraut;
}
addKetchup() {
this.ketchup = false;
console.log("After added Ketchup, it looks like: ", this);
return this;
}
addMustard() {
this.mustard = true;
console.log("After added Mustard, it looks like: ", this);
return this;
}
addKraut() {
this.kraut = true;
console.log("After added Kraut, it looks like: ", this);
return this;
}
}
// new HotDog('wheat', false, true, true); // trditional way
new HotDog("Corn")
.addKetchup()
.addMustard()
.addKraut(); // builder pattern way ~~
// Tips 📝📝: This is how we make method/function chainning together !!!!
9). state pattern
// class Human {
// emotions(mood) {
// switch(mood) {
// case 'happy':
// return 'I am happy » 😁';
// case 'sad':
// return 'I am sad » 😭';
// default:
// return 'I am ok » 😐';
// }
// }
// }
// above code is calling as switch hell ...
class HappyState {
think() {
return "I am happy » 😁";
}
}
class SadState {
think() {
return "I am sad » 😭";
}
}
class OkState {
think() {
return "I am ok » 😐";
}
}
class Human {
state;
constructor() {
this.state = new OkState();
}
think() {
return this.state.think();
}
changeState(newState) {
this.state = newState;
}
}
const damon = new Human();
damon.changeState(new SadState());
damon.changeState(new HappyState());
console.log(damon.think());