Hello, habr!
I was surprised to find that there was no detailed article about the subject on the hub, which immediately prompted me to correct this blatant injustice.

In an environment where the client side of web applications is becoming increasingly thick, business logic is inexorably creeping onto the client, and node.js is increasingly encroaching on the sovereignty of server technologies, one cannot help but think about the techniques of designing architecture in JavaScript. And in this matter, design patterns should undoubtedly help us - template methods for solving frequently encountered problems. Patterns help you build an architecture that requires the least amount of effort from you when changes need to be made. But you should not perceive them as a panacea, i.e., roughly speaking, if the quality of the code is “not great”, it is teeming with hard code and tight connections between logically independent modules, then no patterns will save it. But if the task is to design a scalable architecture, then patterns can be a good help.
However, this article is not about design patterns as such, but about their application in javaScript. In the first part of this article I will write about the use of generative patterns.

Singleton

If the task was to describe this pattern in one phrase, it would turn out something like this: Singleton is a class that can have only one instance.
The simplest and most obvious JavaScript solution for implementing this pattern is to use objects:

Var app = ( property1: "value", property2: "value", ... method1: function () ( ... ), ... )

This method has both its advantages and disadvantages. It is easy to describe, many people use it without realizing the existence of any patterns, and this form of writing will be understandable to any JavaScript developer. But it also has a significant drawback: the main purpose of the singleton pattern is to provide access to an object without using global variables, and this method provides access to the app variable only in the current scope. This means that we can access the app object from anywhere in the application only if it is global. Most often this is extremely unacceptable; good JavaScript development style is to use at most one global variable, which encapsulates everything needed. This means that we can use the above approach at most once in the application.
The second method is a little more complicated, but also more universal:

Function SomeFunction () ( if (typeof (SomeFunction.instance) == "object") ( return SomeFunction.instance; ) this.property1 = "value"; this.property2 = "value"; SomeFunction.instance = this; return this ; ) SomeFunction.prototype.method1 = function () ( )

Now, using any modular system (for example, requirejs), we can connect a file with a description of this constructor function anywhere in our application and get access to our object by executing:

Var someObj = new SomeFunction();

But this method also has its drawback: the instance is stored simply as a static property of the constructor, which allows anyone to overwrite it. We want that, under any circumstances, we can access the required object from any corner of our application. This means that the variable in which we will save the instance must be made private, and closures will help us with this.

Function SomeFunction () ( var instance; SomeFunction = function () ( return instance; ) this.property1 = "value"; this.property2 = "value"; instance = this; )

It would seem that this is the solution to all problems, but new problems take the place of old ones. Namely: all properties included in the constructor prototype after the instance is created will not be available, because in fact, they will be written to the old constructor, and not to the newly defined one. But there is a decent way out of this situation:

Function SomeFunction () ( var instance; SomeFunction = function () ( return instance; ) SomeFunction.prototype = this; instance = new SomeFunction (); instance.constructor = SomeFunction; instance.property1 = "value"; instance.property2 = " value"; return instance; )

This method of describing a singleton is devoid of all the above disadvantages and is quite suitable for universal use, however, methods of describing a singleton using a closure will not work with requirejs, but if you modify them a little and move the variable out of the closure created by the function itself into the function used in define, then the problem will be solved:

Define(, function () ( var instance = null; function SomeFunction() ( if (instance) ( return instance; ) this.property1 = "value"; this.property2 = "value"; instance = this; ); return SomeFunction ; ));

Factory method

The factory method has two main goals:
1) Don't use explicitly concrete classes
2) Combine commonly used object initialization methods together
The simplest implementation of a factory method is this example:

Function Foo () ( //... ) function Bar () ( //... ) function factory (type) ( switch (type) ( case "foo": return new Foo(); case "bar": return new Bar();

Accordingly, the creation of objects will look like this:

Foo = factory("foo"); bar = factory("bar");

A more elegant solution can be used:

Function PetFactory() ( ); PetFactory.register = function(name, PetConstructor) ( if (name instanceof Function) ( PetConstructor = name; name = null; ) if (!(PetConstructor instanceof Function)) ( throw ( name: "Error", message: "PetConstructor is not function" ) ) this = PetConstructor; ); PetFactory.create = function(petName) ( var PetConstructor = this; if (!(PetConstructor instanceof Function)) ( throw ( name: "Error", message: "constructor "" + petName + "" undefined" ) ) return new PetConstructor ();

In this case, we do not limit ourselves to the number of classes that the factory can generate; we can add as many of them as we like in this way:

PetFactory.register("dog", function() ( this.say = function () ( console.log("gav"); ) ));

Or like this:

Function Cat() ( ) Cat.prototype.say = function () ( console.log("meow"); ) PetFactory.register(Cat);

Abstract Factory

An abstract factory is used to create a group of interconnected or interdependent objects.
Suppose we have several pop-up windows that consist of the same elements, but these elements look different and react differently to user actions. Each of these elements will be created using a factory method, which means that each type of pop-up window needs its own object factory.
For example, let's describe the BluePopupFactory factory; it has exactly the same structure as PetFactory, so we'll omit the details and just use it.

Function BluePopup () ( //creating a popup window) BluePopup.prototype.attach = function (elemens) ( //attaching other ui elements to the window) BluePopupFactory.register("popup", BluePopup); function BluePopupButton () ( //creating a button for a blue popup window) BluePopupButton.prototype.setText = function (text) ( //setting the text on the button) BluePopupFactory.register("button", BluePopupButton); function BluePopupTitle () ( //creating a title for the blue window) BluePopupTitle.prototype.setText = function (text) ( //setting the title text) BluePopupFactory.register("title", BluePopupTitle);

We should probably have some kind of class responsible for interface elements.

Function UI () ( //class responsible for ui elements)

And we will add the createPopup method to it:

UI.createPopup = function (factory) ( var popup = factory.create("popup"), buttonOk = factory.create("button"), buttonCancel = factory.create("button"), title = factory.create(" title"); buttonOk.setText("OK"); buttonCancel.setText("Cancel"); title.setText("Untitled"); popup.attach(); )

As you can see, createPopup takes a factory as an argument, creates the popup itself and buttons with a title for it, and then attaches them to the window.
After that you can use this method like this:

Var newPopup = UI.createPopup(BluePopupFactory);

Accordingly, you can describe an unlimited number of factories and pass the one you need when creating the next pop-up window.

Sometimes it is not desirable to instantiate a class directly. Then we resort to generative patterns, which can select the optimal instantiation mechanism.

Simple Factory

Making doors yourself when building a house would be quite difficult, so you get them ready-made from the store.

Pattern A simple factory produces the required copy without bothering the client with the intricacies of this process.

Implementation example

Let's create an implicit interface for all doors:

/* Door getWidth() getHeight() */ class WoodenDoor ( constructor(width, height)( this.width = width this.height = height ) getWidth() ( return this.width ) getHeight() ( return this.height ) )

We will organize a factory that will produce them:

Const DoorFactory = ( makeDoor: (width, height) => new WoodenDoor(width, height) )

That's it, you can work:

Const door = DoorFactory.makeDoor(100, 200) console.log("Width:", door.getWidth()) console.log("Height:", door.getHeight())

The pattern is useful if creating an object requires some logic. It makes sense to move repetitive code into a separate Simple Factory.

Factory method

A recruiting manager works with candidates for various vacancies. Instead of delving into the intricacies of each position, he delegates the technical interview to fellow specialists.

This pattern allows you to create different variations of an object without polluting the constructor with unnecessary code.

Implementation example

Let's start with the hamburger itself:

Class Burger ( constructor(builder) ( this.size = builder.size this.cheeze = builder.cheeze || false this.pepperoni = builder.pepperoni || false this.lettuce = builder.lettuce || false this.tomato = builder .tomato || false ) )

And here is the Builder:

Class BurgerBuilder ( constructor(size) ( this.size = size ) addPepperoni() ( this.pepperoni = true return this ) addLettuce() ( this.lettuce = true return this ) addCheeze() ( this.cheeze = true return this ) addTomato() ( this.tomato = true return this ) build() ( return new Burger(this) ) )

Voila! Here's our burger:

Const burger = (new BurgerBuilder(14)) .addPepperoni() .addLettuce() .addTomato() .build()

The Builder pattern is needed if an object can exist in different variations or the instantiation process consists of several steps.

Singleton

The country must have a single president, otherwise there will be chaos.

This pattern wraps an object and dynamically changes its behavior.

Implementation example

Let's take coffee for example. The simplest coffee that implements the appropriate interface:

/* Coffee interface: getCost() getDescription() */ class SimpleCoffee( getCost() ( return 10 ) getDescription() ( return "Simple coffee" ) )

We want to be able to add various additives to coffee, for this we will create some decorators:

Class MilkCoffee ( constructor(coffee) ( this.coffee = coffee ) getCost() ( return this.coffee.getCost() + 2 ) getDescription() ( return this.coffee.getDescription() + ", milk" ) ) class WhipCoffee ( constructor(coffee) ( this.coffee = coffee ) getCost() ( return this.coffee.getCost() + 5 ) getDescription() ( return this.coffee.getDescription() + ", whip" ) ) class VanillaCoffee ( constructor (coffee) ( this.coffee = coffee ) getCost() ( return this.coffee.getCost() + 3 ) getDescription() ( return this.coffee.getDescription() + ", vanilla" ) )

Now you can make coffee to your taste:

Let someCoffee someCoffee = new SimpleCoffee() console.log(someCoffee.getCost())// 10 console.log(someCoffee.getDescription())// Simple Coffee someCoffee = new MilkCoffee(someCoffee) console.log(someCoffee.getCost( ))// 12 console.log(someCoffee.getDescription())// Plain coffee, milk someCoffee = new WhipCoffee(someCoffee) console.log(someCoffee.getCost())// 17 console.log(someCoffee.getDescription() )// Plain coffee, milk, cream someCoffee = new VanillaCoffee(someCoffee) console.log(someCoffee.getCost())// 20 console.log(someCoffee.getDescription())// Plain coffee, milk, cream, vanilla

Facade

To turn on the computer, just press a button. It's very simple, but there's a lot of complex stuff going on inside the computer that turns on. A simple interface to a complex system is the Façade.

Implementation example

Let's create a computer class:

Class Computer ( getElectricShock() ( console.log("Ouch!") ) makeSound() ( console.log("Beep beep!") ) showLoadingScreen() ( console.log("Loading..") ) bam() ( console.log("Ready to be used!") ) closeEverything() ( console.log("Bup bup bup buzzzz!") ) sooth() ( console.log("Zzzzz") ) pullCurrent() ( console. log("Haaah!") ) )

and a simple Façade for its complex functions:

Class ComputerFacade ( constructor(computer) ( this.computer = computer ) turnOn() ( this.computer.getElectricShock() this.computer.makeSound() this.computer.showLoadingScreen() this.computer.bam() ) turnOff() ( this.computer.closeEverything() this.computer.pullCurrent() this.computer.sooth() ) )

This makes working with a computer much easier:

Const computer = new ComputerFacade(new Computer()) computer.turnOn() // Ouch! Beep beep! Loading.. Ready to be used! computer.turnOff() // Bup bup buzzz! Haah! Zzzzz

Opportunist

On long-distance trains, water for hot drinks is boiled in large containers - for everyone at once. This allows you to save electricity (or gas).

On job search sites you can subscribe to job options that interest you. When a suitable offer appears, the site sends you a notification.

The Observer pattern allows you to notify all interested objects about changes that have occurred.

Implementation example

Applicants want to receive notifications:

Const JobPost = title = (( title: title )) class JobSeeker ( constructor(name) ( this._name = name ) notify(jobPost) ( console.log(this._name, "has been notified of a new posting:", jobPost.title) ) )

And the Notice Board can send these notifications:

Class JobBoard ( constructor() ( this._subscribers = ) subscribe(jobSeeker) ( this._subscribers.push(jobSeeker) ) addJob(jobPosting) ( this._subscribers.forEach(subscriber = ( subscriber.notify(jobPosting) )) ) )

// create subscribers const jonDoe = new JobSeeker("John Doe") const janeDoe = new JobSeeker("Jane Doe") const kaneDoe = new JobSeeker("Kane Doe") // create a message board // sign up applicants const jobBoard = new JobBoard() jobBoard.subscribe(jonDoe) jobBoard.subscribe(janeDoe) // notify subscribers about a new vacancy jobBoard.addJob(JobPost("Software Engineer")) // John Doe has been notified of a new posting: Software Engineer // Jane Doe has been notified of a new posting: Software Engineer

Visitor

To travel abroad, you need to obtain permission (visa). But once in the country, you can safely visit a variety of places without asking for additional permission. You just need to know about them.

The Visitor pattern allows you to add additional operations to objects without changing their source code.

Implementation example

Let's simulate a zoo with different types animals:

Class Monkey ( shout() ( console.log("Ooh oo aa aa!") ) accept(operation) ( operation.visitMonkey(this) ) class Lion ( roar() ( console.log("Roaaar!") ) accept(operation) ( operation.visitLion(this) ) ) class Dolphin ( speak() ( console.log("Tuut tuttu tuutt!") ) accept(operation) ( operation.visitDolphin(this) ) )

Now we want to listen to what sounds they make. To do this, we will create a Visitor:

Const speak = ( visitMonkey(monkey)( monkey.shout() ), visitLion(lion)( lion.roar() ), visitDolphin(dolphin)( dolphin.speak() ) )

It simply accesses each class and calls the desired method:

Const monkey = new Monkey() const lion = new Lion() const dolphin = new Dolphin() monkey.accept(speak) // Ooh oo aa aa! lion.accept(speak) // Roaaar! dolphin.accept(speak) // Tuut tutt tuutt!

The visitor allows not to change existing facilities. With its help, you can, for example, add the ability to jump to all these animals without creating additional methods.

Const jump = ( visitMonkey(monkey) ( console.log("Jumped 20 feet high! on to the tree!") ), visitLion(lion) ( console.log("Jumped 7 feet! Back on the ground!") ) , visitDolphin(dolphin) ( console.log("Walked on water a little and disappeared") ) )

Monkey.accept(speak) // Ooh oo aa aa! monkey.accept(jump) // Jumped 20 feet high! on to the tree! lion.accept(speak) // Roaaar! lion.accept(jump) // Jumped 7 feet! Back on the ground! dolphin.accept(speak) // Tuut tutt tuutt! dolphin.accept(jump) // Walked on water a little and disappeared

Strategy

To organize some set of data, you use a bubble sort algorithm. It copes well with small volumes, but slows down with large ones. Quicksort has the opposite problem. Then you decide to change the algorithm depending on the size of the set. This is your Strategy.

The Strategy template allows you to switch the algorithm used depending on the situation.

Implementation example

First-class functions will help you implement the Strategy in JavaScript.

Const bubbleSort = dataset => ( console.log("Sorting with bubble sort") // ... // ... return dataset ) const quickSort = dataset => ( console.log("Sorting with quick sort") / / ... // ... return dataset )

And this is a client who can use any strategy:

Const sorter = dataset => ( if(dataset.length > 5)( return quickSort ) else ( return bubbleSort ) )

Now you can sort the arrays:

Const longDataSet = const shortDataSet = const sorter1 = sorter(longDataSet) const sorter2 = sorter(shortDataSet) sorter1(longDataSet) // Sorting with quick sort sorter2(shortDataSet) // Sorting with bubble sort

State

You draw in Paint. Depending on your choice, the brush changes its state: it paints in red, blue or any other color.

The State pattern allows you to change the behavior of a class when the state changes.

Implementation example

Let's create text editor, in which you can change the state of the text - bold, italic, etc.

These are the conversion functions:

Const upperCase = inputString => inputString.toUpperCase() const lowerCase = inputString => inputString.toLowerCase() const defaultTransform = inputString => inputString

And here is the editor himself:

Class TextEditor ( constructor(transform) ( this._transform = transform ) setTransform(transform) ( this._transform = transform ) type(words) ( console.log(this._transform(words)) ) )

You can work:

Const editor = new TextEditor(defaultTransform) editor.type("First line") editor.setTransform(upperCase) editor.type("Second line") editor.type("Third line") editor.setTransform(lowerCase) editor.type ("Fourth line") editor.type("Fifth line") // First line // SECOND LINE // THIRD LINE // fourth line // fifth line

Template method

You build a house according to a specific plan: first the foundation, then the walls and only then the roof. The order of these steps cannot be changed, but their implementation may vary.

A template method defines the "skeleton" of the algorithm, but delegates the implementation of the steps to child classes.

Implementation example

Let's create a tool for testing, building and deploying the application.

The base class defines the skeleton of the assembly algorithm:

Class Builder ( // Template method build() ( this.test() this.lint() this.assemble() this.deploy() ) )

And the child classes are the specific implementation of each step:

Class AndroidBuilder extends Builder ( test() ( console.log("Running android tests") ) lint() ( console.log("Linting the android code") ) assemble() ( console.log("Assembling the android build" ) ) deploy() ( console.log("Deploying android build to server") ) ) class IosBuilder extends Builder ( test() ( console.log("Running ios tests") ) lint() ( console.log("Linting the ios code") ) assemble() ( console.log("Assembling the ios build") ) deploy() ( console.log("Deploying ios build to server") ) )

Let's assemble the project:

Const androidBuilder = new AndroidBuilder() androidBuilder.build() // Running android tests // Linting the android code // Assembling the android build // Deploying android build to server const iosBuilder = new IosBuilder() iosBuilder.build() // Running ios tests // Linting the ios code // Assembling the ios build // Deploying ios build to server

  • Translation

Translator's note: The topic of inheritance in JavaScript is one of the most difficult for beginners. With the addition of the new syntax with the class keyword, understanding inheritance clearly has not become easier, although nothing radically new has appeared. This article does not touch on the nuances of implementing prototypal inheritance in JavaScript, so if the reader has any questions, I recommend reading the following articles: Basics and Misconceptions about JavaScript and Understanding OOP in JavaScript [Part 1]

For any comments related to the translation, please contact us in a personal message.

JavaScript is a very powerful language. So powerful that many different ways of designing and creating objects coexist within it. Each method has its pros and cons and I would like to help beginners figure it out. This is a continuation of my previous post, Stop "categorizing" JavaScript. I have received many questions and comments asking for examples, and for this very purpose I decided to write this article.

JavaScript uses prototypical inheritance

This means that in JavaScript objects are inherited from other objects. Simple objects in JavaScript created using () curly braces, have only one prototype: Object.prototype. Object.prototype, in turn, is also an object, and all properties and methods Object.prototype available for all objects.

Arrays created with square brackets, have several prototypes, including Object.prototype And Array.prototype. This means that all properties and methods Object.prototype And Array.prototype available for all arrays. Properties and methods of the same name, for example .valueOf And .ToString, are called from the nearest prototype, in this case from Array.prototype.

Prototype Definitions and Object Creation

Method 1: Template constructor

JavaScript has a special type of function called constructors, which act just like constructors in other languages. Constructor functions are called only using the keyword new and tie up created object with constructor function context via keyword this. A typical constructor might look like this:
function Animal(type)( this.type = type; ) Animal.isAnimal = function(obj, type)( if(!Animal.prototype.isPrototypeOf(obj))( return false; ) return type ? obj.type === type: true); function Dog(name, breed)( Animal.call(this, "dog"); this.name = name; this.breed = breed; ) Object.setPrototypeOf(Dog.prototype, Animal.prototype); Dog.prototype.bark = function())( console.log("ruff, ruff"); ); Dog.prototype.print = function())( console.log("The dog " + this.name + " is a " + this.breed); ); Dog.isDog = function(obj)( return Animal.isAnimal(obj, "dog"); );
Using this constructor looks the same as creating an object in other languages:
var sparkie = new Dog("Sparkie", "Border Collie"); sparkie.name; // "Sparkie" sparkie.breed; // "Border Collie" sparkie.bark(); // console: "ruff, ruff" sparkie.print(); // console: "The dog Sparkie is a Border Collie" Dog.isDog(sparkie); // true
bark And print prototype methods that apply to all objects created using the constructor Dog. Properties name And breed are initialized in the constructor. It is common practice for all methods to be defined in the prototype and properties to be initialized by the constructor.

Method 2: Defining a Class in ES2015 (ES6)

Keyword class has been reserved in JavaScript from the very beginning and now it's finally time to use it. Class definitions in JavaScript are similar to other languages.
class Animal ( constructor(type)( this.type = type; ) static isAnimal(obj, type)( if(!Animal.prototype.isPrototypeOf(obj))( return false; ) return type ? obj.type === type : true; ) ) class Dog extends Animal ( constructor(name, breed)( super("dog"); this.name = name; this.breed = breed; ) bark())( console.log("ruff, ruff" ); ) print() ( console.log("The dog " + this.name + " is a " + this.breed); ) static isDog(obj)( return Animal.isAnimal(obj, "dog"); ) )
Many people find this syntax convenient because it combines the constructor and declaration of static and prototype methods in one block. The usage is exactly the same as in the previous method.
var sparkie = new Dog("Sparkie", "Border Collie");

Method 3: Explicit prototype declaration, Object.create, factory method

This method shows that the new keyword syntax is actually class uses prototypical inheritance. This method also allows you to create a new object without using the operator new.
var Animal = ( create(type)( var animal = Object.create(Animal.prototype); animal.type = type; return animal; ), isAnimal(obj, type)( if(!Animal.prototype.isPrototypeOf(obj) )( return false; ) return type ? obj.type === type: true ), prototype: () ); var Dog = ( create(name, breed)( var proto = Object.assign(Animal.create("dog"), Dog.prototype); var dog = Object.create(proto); dog.name = name; dog. breed = breed; return dog; ), isDog(obj)( return Animal.isAnimal(obj, "dog"); ), prototype: ( bark())( console.log("ruff, ruff"); ), print( )( console.log("The dog " + this.name + " is a " + this.breed); ) ) );
This syntax is convenient because the prototype is declared explicitly. It is clear what is defined in the prototype and what is defined in the object itself. Method Object.create is convenient because it allows you to create an object from a specified prototype. Checking with .isPrototypeOf still works in both cases. The uses are varied, but not excessive:
var sparkie = Dog.create("Sparkie", "Border Collie"); sparkie.name; // "Sparkie" sparkie.breed; // "Border Collie" sparkie.bark(); // console: "ruff, ruff" sparkie.print(); // console: "The dog Sparkie is a Border Collie" Dog.isDog(sparkie); // true

Method 4: Object.create, top-level factory, lazy prototype

This method is a slight modification of Method 3, where the class itself is a factory, as opposed to the case where the class is an object with a factory method. Similar to the constructor example (method 1), but uses a factory method and Object.create.
function Animal(type)( var animal = Object.create(Animal.prototype); animal.type = type; return animal; ) Animal.isAnimal = function(obj, type)( if(!Animal.prototype.isPrototypeOf(obj) )( return false; ) return type ? obj.type === type: true ); Animal.prototype = (); function Dog(name, breed)( var proto = Object.assign(Animal("dog"), Dog.prototype); var dog = Object.create(proto); dog.name = name; dog.breed = breed; return dog; ) Dog.isDog = function(obj)( return Animal.isAnimal(obj, "dog"); ); Dog.prototype = ( bark())( console.log("ruff, ruff"); ), print())( console.log("The dog " + this.name + " is a " + this.breed); ) );
This method is interesting because it is similar to the first method, but does not require a keyword new and works with the operator instanceOf. The usage is the same as in the first method, but without using the keyword new:
var sparkie = Dog("Sparkie", "Border Collie"); sparkie.name; // "Sparkie" sparkie.breed; // "Border Collie" sparkie.bark(); // console: "ruff, ruff" sparkie.print(); // console: "The dog Sparkie is a Border Collie" Dog.isDog(sparkie); // true

Comparison

Method 1 vs Method 4

There are very few reasons to use Method 1 instead of Method 4. Method 1 requires either the use of a keyword new, or adding the following check in the constructor:
if(!(this instanceof Foo))( return new Foo(a, b, c); )
In this case it is easier to use Object.create with factory method. You also can't use functions Function#call or Function#apply with constructor functions because they override keyword context this. The check above may solve this problem, but if you need to work with an unknown number of arguments, you should use a factory method.

Method 2 vs Method 3

Same reasoning about constructors and operator new that were mentioned above are applicable in this case. Checking with instanceof required if new syntax is used class without using an operator new or are used Function#call or Function#apply.

My opinion

A programmer should strive for clarity in his code. The Method 3 syntax makes it very clear what is actually happening. It also makes it easy to use multiple inheritance and stack inheritance. Since the operator new violates the open/closed principle due to incompatibility with apply or call, it should be avoided. Keyword class hides the prototypical nature of inheritance in JavaScript behind the mask of a class system.
“Simple is better than sophisticated,” and using classes because it is considered more “sophisticated” is just an unnecessary, technical headache.

Usage Object.create is more expressive and clear than using copula new And this. Additionally, the prototype is stored in an object that can be outside the context of the factory itself, and thus can be more easily modified and extended by adding methods. Just like classes in ES6.
Keyword class, may be the most detrimental feature of JavaScript. I have enormous respect for the brilliant and very hardworking people who were involved in the process of writing the standard, but even brilliant people sometimes do the wrong thing. -Eric Elliott

Adding something unnecessary and possibly harmful, contrary to the very nature of language, is thoughtless and erroneous.
If you decide to use class, I sincerely hope that I never have to work with your code. In my opinion, developers should avoid using constructors, class And new, and use methods that are more natural to the paradigm and architecture of the language.

Glossary

Object.assign(a, b) copies all enumerable properties of an object b to object a and then returns the object a
Object.create(proto) creates a new object from the specified prototype proto
Object.setPrototypeOf(obj, proto) changes internal property [] object obj on proto

Tags: Add tags

Even though we can write anything in JavaScript, this can lead to problems if we use the wrong design patterns or implement the required pattern incorrectly.

Developers tend to use the latest frameworks and libraries to create web applications, combining two or more of them into one project, and often forgetting the basic ideas behind creating these libraries.

Design patterns are similar to architectural drawings in that they show how to correctly and quickly solve a development problem. software. They are structured using best practices, which help ensure the stability and, in many cases, security of our web applications. Because JavaScript is not a traditional object-oriented programming language, defining design patterns in it may be a little difficult, but not impossible.

In this article, we will discuss various design patterns and how we can best implement them. For brevity we we will only talk about the five most used templates.

Types of Design Patterns

There are many design patterns in software development. These patterns are grouped under three groups, which we will briefly review below:

Generative patterns: These patterns focus on the way objects are created. When creating objects in large applications, there is always a tendency to complicate things. Using generative design patterns solves this problem by controlling the creation of an object.

Structural patterns: Structural patterns provide ways to manage relationships between objects and also create the structure of classes. One way to achieve this is to use inheritance and composition to create a large object from small objects.

Behavioral patterns: Behavioral patterns are patterns that focus on interactions between objects. While generative patterns describe a point in time and structural patterns describe a more or less static structure, behavioral patterns describe a process or flow.

Creating a template module

A module is very often used in software development, and it is an expression consisting of a function that is immediately executed.

// and here we have the code

All module code exists in a private scope. Variables are imported by passing values, and exported by executing a function that returns an object. Modules are useful in large systems because they help keep your global namespace clean and also keep your functions importable and exportable.

An example of an implemented module is shown below:

Const options = (
username: "Michail",
host: "site"
};

Const Configuration = (function(params) (

// returns publicly available data

Return (
login:login
};

Const username = params.username \|\| "",
server = params.server \|\| "",
password = params.password \|\| "";

Function checkPass()
{
if (this.password === "") (
alert("no password!");
return false;
}

Return true;
}

Function checkUsrname()
{

If (this.username === "")
{
alert("no username!");
return false;
}

Return true;
}

Function login()
{
if (checkPass() && checkUsrname())
{
// perform authorization
}
}

))(options);

Please note that the username, hostname, and password values ​​are constantly imported and exported. Using modules provides a clean architecture that will make your code more readable and less buggy.


Close