Written by Hussain Arif✏️
Imagine a situation where a group of architects wants to design a skyscraper. During the design stage, they would have to consider a plethora of factors, for example:
- The architectural style — should the building be brutalist, minimalist, or something else?
- The width of the base — what sizing is needed to prevent collapse during windy days?
- Protection against natural disasters — what preventative structural measures need to be in place based on the location of this building to prevent damage from earthquakes, flooding, etc.?
There can be many factors to consider, but one thing can be known for certain: there’s most likely a blueprint already available to help construct this skyscraper. Without a common design or plan, these architects would have to reinvent the wheel, which can lead to confusion and multiple inefficiencies.
Similarly in the programming world, developers often refer to a set of design patterns to help them build software while following clean code principles. Moreover, these patterns are ubiquitous, thus letting programmers focus on shipping new features instead of reinventing the wheel every time.
In this article, you will learn about a few commonly used JavaScript design patterns, and together we’ll build small Node.js projects to illustrate the usage of each design pattern.
What are design patterns in software engineering?
Design patterns are pre-made blueprints that developers can tailor to solve repetitive design problems during coding. One crucial thing to remember is that these blueprints are not code snippets but rather general concepts to approach incoming challenges.
Design patterns have many benefits:
- Tried and tested — they solve countless problems in software design. Knowing and applying patterns in code is useful because doing so can help you solve all sorts of problems using principles of object-oriented design
- Define a common language — design patterns help teams communicate in an efficient manner. For example, a teammate can say, “We should just use the factory method to solve this issue,” and everyone will understand what they mean and the motive behind their suggestion
In this article, we will cover three categories of design patterns:
- Creational — Used for creating objects
- Structural — Assembling these objects to form a working structure
- Behavioral — Assigning responsibilities between those objects
Let’s see these design patterns in action!
Creational design patterns
As the name suggests, creational patterns comprise various methods to help developers create objects.
Factory
The factory method is a pattern for creating objects that allow more control over object creation. This method is suitable for cases where we want to keep the logic for object creation centralized in one place.
Here is some sample code that showcases this pattern in action:
//file name: factory-pattern.js //use the factory JavaScript design pattern: //Step 1: Create an interface for our object. In this case, we want to create a car const createCar = ({ company, model, size }) => ({ //the properties of the car: company, model, size, //a function that prints out the car's properties: showDescription() { console.log( "The all new ", model, " is built by ", company, " and has an engine capacity of ", size, " CC " ); }, }); //Use the 'createCar' interface to create a car const challenger = createCar({ company: "Dodge", model: "Challenger", size: 6162, }); //print out this object's traits: challenger.showDescription();
Let’s break down this code piece by piece:createCarCar
- Each Car has three properties: company , model and size. Additionally, we have also defined a showDescription function, which will log out the properties of the object. Furthermore, notice that the createCar method demonstrates how we can have granular control when it comes to instantiating objects in memory
- Later on, we used our createCar instance to initialize an object called challenger
- Finally, in the last line, we invoked the showDescription on our challenger instance
Let’s test it out! We should expect the program to log out the details of our newly-created Car instance:
Builder
The builder method lets us build objects using step-by-step object construction. As a result, this design pattern is great for situations where we want to create an object and only apply necessary functions. As a result, this allows for greater flexibility.
Here is a block of code that uses the builder pattern to create a Car object:
//builder-pattern.js //Step 1: Create a class reperesentation for our toy car: class Car { constructor({ model, company, size }) { this.model = model; this.company = company; this.size = size; } } //Use the 'builder' pattern to extend this class and add functions //note that we have seperated these functions in their entities. //this means that we have not defined these functions in the 'Car' definition. Car.prototype.showDescription = function () { console.log( this.model + " is made by " + this.company + " and has an engine capacity of " + this.size + " CC " ); }; Car.prototype.reduceSize = function () { const size = this.size - 2; //function to reduce the engine size of the car. this.size = size; }; const challenger = new Car({ company: "Dodge", model: "Challenger", size: 6162, }); //finally, print out the properties of the car before and after reducing the size: challenger.showDescription(); console.log('reducing size...'); //reduce size of car twice: challenger.reduceSize(); challenger.reduceSize(); challenger.showDescription();
Here’s what we’re doing in the code block above:
- As a first step, we created a Car class which will help us instantiate objects. Notice that earlier in the factory pattern, we used a createCar function, but here we are using classes. This is because classes in JavaScript let developers construct objects in pieces. Or, in simpler words, to implement the JavaScript builder design pattern, we have to opt for the object-oriented paradigm
- Afterwards, we used the prototype object to extend the Car class. Here, we created two functions — showDescription and reduceSize
- Later on, we then created our Car instance, named it challenger, and then logged out its information
- Finally, we invoked the reduceSize method on this object to decrement its size, and then we printed its properties once more
The expected output should be the properties of the challenger object before and after we reduced its size by four units: This confirms that our builder pattern implementation in JavaScript was successful!
Structural design patterns
Structural design patterns focus on how different components of our program work together.
Adapter
The adapter method allows objects with conflicting interfaces to work together. A great use case for this pattern is when we want to adapt old code to a new codebase without introducing breaking changes:
//adapter-pattern.js //create an array with two fields: //'name' of a band and the number of 'sold' albums const groupsWithSoldAlbums = [ { name: "Twice", sold: 23, }, { name: "Blackpink", sold: 23 }, { name: "Aespa", sold: 40 }, { name: "NewJeans", sold: 45 }, ]; console.log("Before:"); console.log(groupsWithSoldAlbums); //now we want to add this object to the 'groupsWithSoldAlbums' //problem: Our array can't accept the 'revenue' field // we want to change this field to 'sold' var illit = { name: "Illit", revenue: 300 }; //Solution: Create an 'adapter' to make both of these interfaces.. //..work with each other const COST_PER_ALBUM = 30; const convertToAlbumsSold = (group) => { //make a copy of the object and change its properties const tempGroup = { name: group.name, sold: 0 }; tempGroup.sold = parseInt(group.revenue / COST_PER_ALBUM); //return this copy: return tempGroup; }; //use our adapter to make a compatible copy of the 'illit' object: illit = convertToAlbumsSold(illit); //now that our interfaces are compatible, we can add this object to the array groupsWithSoldAlbums.push(illit); console.log("After:"); console.log(groupsWithSoldAlbums);
Here’s what’s happening in this snippet:
- First, we created an array of objects called groupsWithSoldAlbums. Each object will have a name and sold property
- We then made an illit object which had two properties — name and revenue. Here, we want to append this to the groupsWithSoldAlbums array. This might be an issue, since the array doesn’t accept a revenue property
- To mitigate this problem, use the adapter method. The convertToAlbumsSold function will adjust the illit object so that it can be added to our array
When this code is run, we expect our illit object to be part of the groupsWithSoldAlbums list:
Decorator
This design pattern lets you add new methods and properties to objects after creation. This is useful when we want to extend the capabilities of a component during runtime.
If you come from a React background, this is similar to using Higher Order Components. Here is a block of code that demonstrates the use of the JavaScript decorator design pattern:
//file name: decorator-pattern.js //Step 1: Create an interface class MusicArtist { constructor({ name, members }) { this.name = name; this.members = members; } displayMembers() { console.log( "Group name", this.name, " has", this.members.length, " members:" ); this.members.map((item) => console.log(item)); } } //Step 2: Create another interface that extends the functionality of MusicArtist class PerformingArtist extends MusicArtist { constructor({ name, members, eventName, songName }) { super({ name, members }); this.eventName = eventName; this.songName = songName; } perform() { console.log( this.name + " is now performing at " + this.eventName + " They will play their hit song " + this.songName ); } } //create an instance of PerformingArtist and print out its properties: const akmu = new PerformingArtist({ name: "Akmu", members: ["Suhyun", "Chanhyuk"], eventName: "MNET", songName: "Hero", }); akmu.displayMembers(); akmu.perform();
Let's explain what's happening here:
- In the first step, we created a MusicArtist class which has two properties: name and members. It also has a displayMembers method, which will print out the name and the members of the current music band
- Later on, we extended MusicArtist and created a child class called PerformingArtist. In addition to the properties of MusicArtist, the new class will have two more properties: eventName and songName. Furthermore, PerformingArtist also has a perform function, which will print out the name and the songName properties to the console
- Afterwards, we created a PerformingArtist instance and named it akmu
- Finally, we logged out the details of akmu and invoked the perform function
The output of the code should confirm that we successfully added new capabilities to our music band via the PerformingArtist class:
Behavioral design patterns
This category focuses on how different components in a program communicate with each other.
Chain of Responsibility
The Chain of Responsibility design pattern allows for passing requests through a chain of components. When the program receives a request, components in the chain either handle it or pass it on until the program finds a suitable handler.
Here’s an illustration that explains this design pattern: The bucket, or request, is passed down the chain of components until a capable component is found. When a suitable component is found, it will process the request. Source: Refactoring Guru.[/caption] The best use for this pattern is a chain of Express middleware functions, where a function would either process an incoming request or pass it to the next function via the next() method:
//Real-world situation: Event management of a concert //implement COR JavaScript design pattern: //Step 1: Create a class that will process a request class Leader { constructor(responsibility, name) { this.responsibility = responsibility; this.name = name; } //the 'setNext' function will pass the request to the next component in the chain. setNext(handler) { this.nextHandler = handler; return handler; } handle(responsibility) { //switch to the next handler and throw an error message: if (this.nextHandler) { console.log(this.name + " cannot handle operation: " + responsibility); return this.nextHandler.handle(responsibility); } return false; } } //create two components to handle certain requests of a concert //first component: Handle the lighting of the concert: class LightsEngineerLead extends Leader { constructor(name) { super("Light management", name); } handle(responsibility) { //if 'LightsEngineerLead' gets the responsibility(request) to handle lights, //then they will handle it if (responsibility == "Lights") { console.log("The lights are now being handled by ", this.name); return; } //otherwise, pass it to the next component. return super.handle(responsibility); } } //second component: Handle the sound management of the event: class SoundEngineerLead extends Leader { constructor(name) { super("Sound management", name); } handle(responsibility) { //if 'SoundEngineerLead' gets the responsibility to handle sounds, // they will handle it if (responsibility == "Sound") { console.log("The sound stage is now being handled by ", this.name); return; } //otherwise, forward this request down the chain: return super.handle(responsibility); } } //create two instances to handle the lighting and sounds of an event: const minji = new LightsEngineerLead("Minji"); const danielle = new SoundEngineerLead("Danielle"); //set 'danielle' to be the next handler component in the chain. minji.setNext(danielle); //ask Minji to handle the Sound and Lights: //since Minji can't handle Sound Management, // we expect this request to be forwarded minji.handle("Sound"); //Minji can handle Lights, so we expect it to be processed minji.handle("Lights");
In the above code, we’ve modeled a situation at a music concert. Here, we want different people to handle different responsibilities. If a person cannot handle a certain task, it’s delegated to the next person in the list.
Initially, we declared a Leader base class with two properties:
- responsibility — the kind of task the leader can handle
- name — the name of the handler
Additionally, each Leader will have two functions:
- setNext: As the name suggests, this function will add a Leader to the responsibility chain
- handle: The function will check if the current Leader can process a certain responsibility; otherwise, it will forward that responsibility to the next person via the setNext method
Next, we created two child classes called LightsEngineerLead (responsible for lighting), and SoundEngineerLead (handles audio). Later on, we initialized two objects — minji and danielle. We used the setNext function to set danielle as the next handler in the responsibility chain.
Lastly, we asked minji to handle Sound and Lights.
When the code is run, we expect minji to attempt at processing our Sound and Light responsibilities. Since minji is not an audio engineer, it should hand over Sound to a capable handler. In this case, it is danielle:
Strategy
The strategy method lets you define a collection of algorithms and swap between them during runtime. This pattern is useful for navigation apps. These apps can leverage this pattern to switch between routes for different user types (cycling, driving, or running):
This code block demonstrates the strategy design pattern in JavaScript code:
//situation: Build a calculator app that executes an operation between 2 numbers. //depending on the user input, change between division and modulus operations class CalculationStrategy { performExecution(a, b) {} } //create an algorithm for division class DivisionStrategy extends CalculationStrategy { performExecution(a, b) { return a / b; } } //create another algorithm for performing modulus class ModuloStrategy extends CalculationStrategy { performExecution(a, b) { return a % b; } } //this class will help the program switch between our algorithms: class StrategyManager { setStrategy(strategy) { this.strategy = strategy; } executeStrategy(a, b) { return this.strategy.performExecution(a, b); } } const moduloOperation = new ModuloStrategy(); const divisionOp = new DivisionStrategy(); const strategyManager = new StrategyManager(); //use the division algorithm to divide two numbers: strategyManager.setStrategy(divisionOp); var result = strategyManager.executeStrategy(20, 4); console.log("Result is: ", result); //switch to the modulus strategy to perform modulus: strategyManager.setStrategy(moduloOperation); result = strategyManager.executeStrategy(20, 4); console.log("Result of modulo is ", result);
Here’s what we did in the above block:
- First we created a base CalculationStrategy abstract class which will process two numbers — a and b
- We then defined two child classes — DivisionStrategy and ModuloStrategy. These two classes consist of division and modulo algorithms and return the output
- Next, we declared a StrategyManager class which will let the program alternate between different algorithms
- In the end, we used our DivisionStrategy and ModuloStrategy algorithms to process two numbers and return its output. To switch between these strategies, the strategyManager instance was used
When we execute this program, the expected output is strategyManager first using DivisionStrategy to divide two numbers and then switching to ModuloStrategy to return the modulo of those inputs:
Conclusion
In this article, we learned about what design patterns are, and why they are useful in the software development industry. Furthermore, we also learned about different categories of JavaScript design patterns and implemented them in code.
LogRocket: Debug JavaScript errors more easily by understanding the context
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Try it for free.
The above is the detailed content of JavaScript design patterns guide. For more information, please follow other related articles on the PHP Chinese website!

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript's applications in the real world include server-side programming, mobile application development and Internet of Things control: 1. Server-side programming is realized through Node.js, suitable for high concurrent request processing. 2. Mobile application development is carried out through ReactNative and supports cross-platform deployment. 3. Used for IoT device control through Johnny-Five library, suitable for hardware interaction.

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

Atom editor mac version download
The most popular open source editor

SublimeText3 Linux new version
SublimeText3 Linux latest version

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),