Do you know the common JavaScript design patterns?

Original: https://medium.com/better-programming...
Translator: Front-end Technology Brother

brief introduction

We write code to solve problems. These problems usually have many similarities. When we try to solve these problems, we will notice several common patterns. This is where design patterns come into play.
The term design pattern is used in software engineering to represent a common and reusable solution to problems that often arise in software design.
The basic concepts of design patterns have existed in the software engineering industry from the beginning, but they are not really formalized. The book Design Patterns: The Foundation of Reusable Object-Oriented Software, written by Erich Gamma, Richard Helm, Ralph Johnson and John Vlisides, the famous Gang of Four (GoF), helps to promote the formal concept of design patterns in software engineering. Nowadays, design patterns are an important part of software development, and they have existed for a long time.
The original book introduces 23 design patterns.


There are many benefits of design patterns. They are proven solutions that industry veterans have tried and tested. They are reliable ways to solve problems in a widely accepted way and reflect the experience and insights of leading developers in the industry who help define problems. Patterns also make our code more reusable and readable, while greatly speeding up the development process.
Design patterns are by no means the final solution. They just provide us with solutions or solutions to problems.
Note: In this article, we will discuss design patterns mainly from an object-oriented perspective and their usability in modern JavaScript. That's why many classical patterns from GoF will not be mentioned, and some modern patterns from Addy Osmani's JavaScript Design Patterns will be included. For ease of understanding, these examples are kept simple and therefore not the best implementation of their respective design patterns.

Categories of Design Patterns

Design patterns are usually divided into three categories.

Creative Patterns

As the name implies, these patterns are used to handle object creation mechanisms. Creative design mode basically solves problems by controlling the process of creating objects.
We will discuss the following models in detail: builder model, factory model, prototype model and singleton model.

Structural model

These patterns are related to class and object combinations. They help to construct or reorganize one or more parts without affecting the whole system. In other words, they help to acquire new functions without changing existing ones.
We will discuss the following modes in detail: adapter mode, composite mode, decoration mode, appearance mode, hedonic mode and proxy mode.

Behavioral patterns

These patterns involve improving communication between different objects.
We will discuss in detail the following modes: responsibility chain mode, command mode, iterator mode, mediator mode, observer mode, state mode, policy mode and template method mode.

Builder pattern

This is a class-based creative design pattern. A constructor is a special function that can be used to instantiate new objects with methods and attributes defined by the function.
It is not one of the classical design patterns. In fact, in most object-oriented languages, it is more like a basic language construction than a pattern. But in JavaScript, objects can be created dynamically without any constructor or "class" definition. Therefore, I think it is very important to lay the foundation for other models with this simple model.
The constructor pattern is one of the most commonly used patterns in JavaScript for creating new objects of a given type.
In the following example, we define a class Hero, which has properties such as name and special Ability, and methods such as getDetails. Then, we instantiate an object IronMan by calling the constructor method, which uses the new keyword as a parameter to pass in the value of the corresponding attribute.

// traditional Function-based syntax
function Hero(name, specialAbility) {
  // setting property values
  this.name = name;
  this.specialAbility = specialAbility;

  // declaring a method on the object
  this.getDetails = function() {
    return this.name + ' can ' + this.specialAbility;
  };
}

// ES6 Class syntax
class Hero {
  constructor(name, specialAbility) {
    // setting property values
    this._name = name;
    this._specialAbility = specialAbility;

    // declaring a method on the object
    this.getDetails = function() {
      return `${this._name} can ${this._specialAbility}`;
    };
  }
}
// creating new instances of Hero
const IronMan = new Hero('Iron Man', 'fly');
console.log(IronMan.getDetails()); // Iron Man can fly

Factory model

The factory model is another class-based creation model. Here, we provide a generic interface that delegates responsibility for object instantiation to its subclasses.
This pattern is often used when we need to manage or manipulate different sets of objects with many similar features.
In the following example, we create a factory class called BallFactory, which has a method for accepting parameters, according to which it delegates responsibility for object instantiation to the corresponding class. If the type parameter is "football" or "soccer" object instantiation, it is handled by the Football class, but if the type parameter is "basketball" object instantiation, it is handled by the Basketball class.

class BallFactory {
  constructor() {
    this.createBall = function(type) {
      let ball;
      if (type === 'football' || type === 'soccer') ball = new Football();
      else if (type === 'basketball') ball = new Basketball();
      ball.roll = function() {
        return `The ${this._type} is rolling.`;
      };

      return ball;
    };
  }
}

class Football {
  constructor() {
    this._type = 'football';
    this.kick = function() {
      return 'You kicked the football.';
    };
  }
}

class Basketball {
  constructor() {
    this._type = 'basketball';
    this.bounce = function() {
      return 'You bounced the basketball.';
    };
  }
}

// creating objects
const factory = new BallFactory();

const myFootball = factory.createBall('football');
const myBasketball = factory.createBall('basketball');

console.log(myFootball.roll()); // The football is rolling.
console.log(myBasketball.roll()); // The basketball is rolling.
console.log(myFootball.kick()); // You kicked the football.
console.log(myBasketball.bounce()); // You bounced the basketball.

Prototype pattern

This is an object-based creative design pattern. Here, we use some "skeleton" of existing objects to create or instantiate new objects.
This pattern is particularly important and useful for JavaScript because it uses prototype inheritance rather than classic object-oriented inheritance. Therefore, it takes advantage of JavaScript and has native support.
In the following example, we use an object car as a prototype and create another object myCar with the Object.create feature of JavaScript. And define an additional attribute owner on the new object.

// using Object.create as was recommended by ES5 standard
const car = {
  noOfWheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};

// Object.create(proto[, propertiesObject])

const myCar = Object.create(car, { owner: { value: 'John' } });

console.log(myCar.__proto__ === car); // true

Adapter mode

This is a structural pattern in which the interface of one class is converted to another. This pattern allows classes that cannot work together because of incompatible interfaces.
This pattern is usually used to create wrappers for new refactoring APIs so that other existing old APIs can still use them. This is usually done when new implementations or code refactoring (for performance reasons, etc.) lead to different public APIs, while other parts of the system are still using the old APIs and need to be adjusted to work together.
In the following example, we have an old API, the Old Calculator class, and a new API, the New Calculator class. Class OldCalculator provides an operation method for addition and subtraction, while New Calculator provides a separate method for addition and subtraction. The adapter class CalcAdapter wraps the New Calculator package to add operation methods to the public-oriented API, while using its own addition and subtraction at the bottom.

// old interface
class OldCalculator {
  constructor() {
    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          return term1 + term2;
        case 'sub':
          return term1 - term2;
        default:
          return NaN;
      }
    };
  }
}

// new interface
class NewCalculator {
  constructor() {
    this.add = function(term1, term2) {
      return term1 + term2;
    };
    this.sub = function(term1, term2) {
      return term1 - term2;
    };
  }
}

// Adapter Class
class CalcAdapter {
  constructor() {
    const newCalc = new NewCalculator();

    this.operations = function(term1, term2, operation) {
      switch (operation) {
        case 'add':
          // using the new implementation under the hood
          return newCalc.add(term1, term2);
        case 'sub':
          return newCalc.sub(term1, term2);
        default:
          return NaN;
      }
    };
  }
}

// usage
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15

const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15

const adaptedCalc = new CalcAdapter();
console.log(adaptedCalc.operations(10, 5, 'add')); // 15;

Compound mode

This is a structural design pattern, which combines objects into a tree structure to represent the hierarchy of the whole part. In this pattern, each node in the class tree structure can be either a single object or a composite set of objects. In any case, each node is processed uniformly.


Visualizing this pattern is a bit complicated. The easiest way to think about this is to use examples of multi-level menus. Each node can be either a different option or the menu itself, and its child nodes have multiple options. Node components with sub-components are composite components, while node components without sub-components are leaf components.
In the following example, we create a Component base class that implements the required common functions and abstracts the required other methods. The base class also has a static method, which uses recursive traversal of the composite tree structure composed of subclasses. Then, we create two subclasses to extend the base class -- Leaf that does not contain any subclasses and Composite that can contain subclasses -- so there is a way to handle adding, searching, and deleting subclasses. These two subclasses are used to create composite structures -- in this case, trees.

class Component {
  constructor(name) {
    this._name = name;
  }

  getNodeName() {
    return this._name;
  }

  // abstract methods that need to be overridden
  getType() {}

  addChild(component) {}

  removeChildByName(componentName) {}

  removeChildByIndex(index) {}

  getChildByName(componentName) {}

  getChildByIndex(index) {}

  noOfChildren() {}

  static logTreeStructure(root) {
    let treeStructure = '';
    function traverse(node, indent = 0) {
      treeStructure += `${'--'.repeat(indent)}${node.getNodeName()}\n`;
      indent++;
      for (let i = 0, length = node.noOfChildren(); i < length; i++) {
        traverse(node.getChildByIndex(i), indent);
      }
    }

    traverse(root);
    return treeStructure;
  }
}

class Leaf extends Component {
  constructor(name) {
    super(name);
    this._type = 'Leaf Node';
  }

  getType() {
    return this._type;
  }

  noOfChildren() {
    return 0;
  }
}

class Composite extends Component {
  constructor(name) {
    super(name);
    this._type = 'Composite Node';
    this._children = [];
  }

  getType() {
    return this._type;
  }

  addChild(component) {
    this._children = [...this._children, component];
  }

  removeChildByName(componentName) {
    this._children = [...this._children].filter(component => component.getNodeName() !== componentName);
  }

  removeChildByIndex(index) {
    this._children = [...this._children.slice(0, index), ...this._children.slice(index + 1)];
  }

  getChildByName(componentName) {
    return this._children.find(component => component.name === componentName);
  }

  getChildByIndex(index) {
    return this._children[index];
  }

  noOfChildren() {
    return this._children.length;
  }
}

// usage
const tree = new Composite('root');
tree.addChild(new Leaf('left'));
const right = new Composite('right');
tree.addChild(right);
right.addChild(new Leaf('right-left'));
const rightMid = new Composite('right-middle');
right.addChild(rightMid);
right.addChild(new Leaf('right-right'));
rightMid.addChild(new Leaf('left-end'));
rightMid.addChild(new Leaf('right-end'));

// log
console.log(Component.logTreeStructure(tree));
/*
root
--left
--right
----right-left
----right-middle
------left-end
------right-end
----right-right
*/

Decorator mode

class Book {
  constructor(title, author, price) {
    this._title = title;
    this._author = author;
    this.price = price;
  }

  getDetails() {
    return `${this._title} by ${this._author}`;
  }
}

// decorator 1
function giftWrap(book) {
  book.isGiftWrapped = true;
  book.unwrap = function() {
    return `Unwrapped ${book.getDetails()}`;
  };

  return book;
}

// decorator 2
function hardbindBook(book) {
  book.isHardbound = true;
  book.price += 5;
  return book;
}

// usage
const alchemist = giftWrap(new Book('The Alchemist', 'Paulo Coelho', 10));

console.log(alchemist.isGiftWrapped); // true
console.log(alchemist.unwrap()); // 'Unwrapped The Alchemist by Paulo Coelho'

const inferno = hardbindBook(new Book('Inferno', 'Dan Brown', 15));

console.log(inferno.isHardbound); // true
console.log(inferno.price); // 20

Appearance pattern

This is a widely used structural design pattern in JavaScript libraries. It is used to provide a unified, simpler, public-oriented interface for easy use, thus avoiding the complexity of its subsystems or subclasses.
The use of this pattern is common in libraries like jQuery.
In this example, we created a public-oriented API that contains a class ComplaintRegistry. It only exposes one method the client will use, registerComplaint. It internally handles the instantiation of objects required by Product Complaint or Service Complaint based on type parameters. It also handles all other complex functions, such as generating a unique ID, storing complaints in memory, and so on. However, the use of appearance patterns hides all these complexities.

let currentId = 0;
class ComplaintRegistry {
  registerComplaint(customer, type, details) {
    const id = ComplaintRegistry._uniqueIdGenerator();
    let registry;
    if (type === 'service') {
      registry = new ServiceComplaints();
    } else {
      registry = new ProductComplaints();
    }
    return registry.addComplaint({ id, customer, details });
  }

  static _uniqueIdGenerator() {
    return ++currentId;
  }
}

class Complaints {
  constructor() {
    this.complaints = [];
  }

  addComplaint(complaint) {
    this.complaints.push(complaint);
    return this.replyMessage(complaint);
  }

  getComplaint(id) {
    return this.complaints.find(complaint => complaint.id === id);
  }

  replyMessage(complaint) {}
}

class ProductComplaints extends Complaints {
  constructor() {
    super();
    if (ProductComplaints.exists) {
      return ProductComplaints.instance;
    }
    ProductComplaints.instance = this;
    ProductComplaints.exists = true;
    return this;
  }

  replyMessage({ id, customer, details }) {
    return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Products Complaint Department. Replacement/Repairment of the product as per terms and conditions will be carried out soon.`;
  }
}

class ServiceComplaints extends Complaints {
  constructor() {
    super();
    if (ServiceComplaints.exists) {
      return ServiceComplaints.instance;
    }
    ServiceComplaints.instance = this;
    ServiceComplaints.exists = true;
    return this;
  }

  replyMessage({ id, customer, details }) {
    return `Complaint No. ${id} reported by ${customer} regarding ${details} have been filed with the Service Complaint Department. The issue will be resolved or the purchase will be refunded as per terms and conditions.`;
  }
}

// usage
const registry = new ComplaintRegistry();

const reportService = registry.registerComplaint('Martha', 'service', 'availability');
// 'Complaint No. 1 reported by Martha regarding availability have been filed with the Service Complaint Department. The issue will be resolved or the purchase will be refunded as per terms and conditions.'

const reportProduct = registry.registerComplaint('Jane', 'product', 'faded color');
// 'Complaint No. 2 reported by Jane regarding faded color have been filed with the Products Complaint Department. Replacement/Repairment of the product as per terms and conditions will be carried out soon.'

Hedonic Model

This is a structural design pattern that focuses on effective data sharing through fine-grained objects. It is used for improving efficiency and memory preservation purposes.
This pattern can be used for any type of caching purposes. In fact, modern browsers use variants of the hedge mode to prevent the same image from being loaded twice.
In the following example, we created a fine-grained hedge class, Icecream, to share data about ice cream flavors, and a factory-level IcecreamFactory to create these hedge class objects. For memory retention, if the same object is instantiated twice, the object is reclaimed.
This is a simple example of Heyuan implementation.

// flyweight class
class Icecream {
  constructor(flavour, price) {
    this.flavour = flavour;
    this.price = price;
  }
}

// factory for flyweight objects
class IcecreamFactory {
  constructor() {
    this._icecreams = [];
  }

  createIcecream(flavour, price) {
    let icecream = this.getIcecream(flavour);
    if (icecream) {
      return icecream;
    } else {
      const newIcecream = new Icecream(flavour, price);
      this._icecreams.push(newIcecream);
      return newIcecream;
    }
  }

  getIcecream(flavour) {
    return this._icecreams.find(icecream => icecream.flavour === flavour);
  }
}

// usage
const factory = new IcecreamFactory();

const chocoVanilla = factory.createIcecream('chocolate and vanilla', 15);
const vanillaChoco = factory.createIcecream('chocolate and vanilla', 15);

// reference to the same object
console.log(chocoVanilla === vanillaChoco); // true

proxy pattern

This is a structural design pattern whose behavior fully conforms to its name. It acts as a proxy or placeholder for another object to control access to it.
It is usually used when the target object is constrained and may not be able to effectively handle all its responsibilities. In this case, the proxy usually provides the same interface to the client and adds an indirect layer to support controlled access to the target object to avoid putting too much pressure on the target object.
Proxy mode is very useful in dealing with applications with more network requests, which can avoid unnecessary or redundant network requests.
In the following example, we will use two new ES6 features, Proxy and Reflect. Proxy objects are used to define custom behaviors for the basic operations of JavaScript objects (remember that functions and arrays are also objects in JavaScript). It is a constructor method that can be used to create object Proxy. It accepts the target of the object to be proxied and defines the necessary customized handler objects. Handler objects allow you to define trap functions, such as get, set, has, apply, etc., that are used to add custom behavior to their usage. Reflect, on the other hand, is a built-in object that provides methods similar to Proxy's handler object support as static methods. It is not a constructor; its static methods are used for interceptable JavaScript operations.
Now, let's create a function that can be viewed as a network request. We named it networkFetch. It accepts a URL and responds accordingly. We want to implement a proxy in which we can get a response from the network only if there is no network response in the cache. Otherwise, we only return a response from the cache.
The global variable cache stores the cached response. We created a proxied Network Fetch proxy, using the original network Fetch as a target, and using the application method to proxy function calls in the object handler. The apply method is passed on the object target itself. This value acts as thisArg, and the parameter is passed to it in an array-like structure, args.
We check whether the url parameter passed is in the cache. If it exists in the cache, we will return the response from there without calling the original target function. If not, then we use the Reflect.apply method to call the function target and the parameters it passes with thisArg (although it doesn't make any sense in our example).

// Target
function networkFetch(url) {
  return `${url} - Response from network`;
}

// Proxy
// ES6 Proxy API = new Proxy(target, handler);
const cache = [];
const proxiedNetworkFetch = new Proxy(networkFetch, {
  apply(target, thisArg, args) {
    const urlParam = args[0];
    if (cache.includes(urlParam)) {
      return `${urlParam} - Response from cache`;
    } else {
      cache.push(urlParam);
      return Reflect.apply(target, thisArg, args);
    }
  },
});

// usage
console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from network'
console.log(proxiedNetworkFetch('dogPic.jpg')); // 'dogPic.jpg - Response from cache'

Chain of Responsibility Model

This is a behavioral design pattern that provides loosely coupled object chains. Each of these objects has the option of processing client requests.
A good example of the responsibility chain pattern is the event bubbling mechanism in DOM, where an event is propagated through a series of nested DOM elements, one of which may have an "event listener" attached to listen for and operate on the event.
In the following example, we create a Cumulative Sum that can be instantiated using an optional initial value. It has a method add that adds the passed value to the object's sum attribute and returns the object itself to allow link add method calls.
This common pattern can also be seen in jQuery, where almost any method call to a jQuery object returns a jQuery object to link method calls together.

class CumulativeSum {
  constructor(intialValue = 0) {
    this.sum = intialValue;
  }

  add(value) {
    this.sum += value;
    return this;
  }
}

// usage
const sum1 = new CumulativeSum();
console.log(sum1.add(10).add(2).add(50).sum); // 62


const sum2 = new CumulativeSum(10);
console.log(sum2.add(10).add(20).add(5).sum); // 45

Command mode

This is a behavioral design pattern designed to encapsulate operations or operations as objects. This pattern allows loosely coupled systems and classes by separating objects that request operations or call methods from objects that execute or process actual implementations.
The Clipboard Interaction API is somewhat similar to command mode. If you are a Redux user, you have encountered command mode. The operation that enables us to go back to the previous time node is simply encapsulating the traceable operations for redo or undo. Therefore, we have realized time travel debugging.
In the following example, we have a class named SpecialMath, which has multiple methods and a Command class that encapsulates an object of the SpecialMath class, the command to be executed on its subject. The Command class also tracks all executed commands that can be used to extend its functionality to include undo and redo type operations.

class SpecialMath {
  constructor(num) {
    this._num = num;
  }

  square() {
    return this._num ** 2;
  }

  cube() {
    return this._num ** 3;
  }

  squareRoot() {
    return Math.sqrt(this._num);
  }
}

class Command {
  constructor(subject) {
    this._subject = subject;
    this.commandsExecuted = [];
  }
  execute(command) {
    this.commandsExecuted.push(command);
    return this._subject[command]();
  }
}

// usage
const x = new Command(new SpecialMath(5));
x.execute('square');
x.execute('cube');

console.log(x.commandsExecuted); // ['square', 'cube']

Iterator pattern

It is a behavioral design pattern that provides a way to access elements of aggregated objects sequentially without exposing their internal representations.
Iterators have a special behavior in which we traverse an ordered set of values once by calling next() until we reach the end. The introduction of iterator and generator in ES6 makes the implementation of iterator pattern very simple.
Here are two examples. First, one Iterator Class uses the iterator specification, while the other iterator UsingGenerator uses generator functions.
Symbol.iterator (Symbol - a new basic data type) is used to specify the default iterator for an object. It must be defined as a collection so that it can use the loop structure for...of. In the first example, we define constructors to store some data sets, and then define the symbol Symbol.iterator, which returns an object and the next method for iteration.
For the second case, we define a generator function that passes an array of data to it and iteratively returns its elements using next and field. Generator function is a special type of function. It acts as the factory of iterator and can maintain its internal state and generate value iteratively and explicitly. It can pause and resume its execution cycle.

Intermediary model

It is a behavioral design pattern that encapsulates how a set of objects interact with each other. It provides central privileges for a group of objects by promoting loose coupling and preventing objects from explicitly referencing each other.
In the following example, we use TrafficTower as a mediator to control how Airplane objects interact. All Airplane objects will register themselves in a TrafficTower object, and the mediation object handles how the Airplane object receives coordinate data from all other Airplane objects.

class TrafficTower {
  constructor() {
    this._airplanes = [];
  }

  register(airplane) {
    this._airplanes.push(airplane);
    airplane.register(this);
  }

  requestCoordinates(airplane) {
    return this._airplanes.filter(plane => airplane !== plane).map(plane => plane.coordinates);
  }
}

class Airplane {
  constructor(coordinates) {
    this.coordinates = coordinates;
    this.trafficTower = null;
  }

  register(trafficTower) {
    this.trafficTower = trafficTower;
  }

  requestCoordinates() {
    if (this.trafficTower) return this.trafficTower.requestCoordinates(this);
    return null;
  }
}

// usage
const tower = new TrafficTower();

const airplanes = [new Airplane(10), new Airplane(20), new Airplane(30)];
airplanes.forEach(airplane => {
  tower.register(airplane);
});

console.log(airplanes.map(airplane => airplane.requestCoordinates())) 
// [[20, 30], [10, 30], [10, 20]]

Observer model

It is a crucial behavioral design pattern that defines one-to-many dependencies between objects so that when an object (publisher) changes its state, all other dependent objects (subscribers) are notified and updated automatically. This is also known as PubSub (Publisher/Subscriber) or event dispatcher/listener mode. Publishers are sometimes called topics and subscribers are sometimes called observers.
If you've ever written even-numbered processing code using addEventListener or jQuery's. on, you might already be familiar with this pattern. It also has an impact on reactive programming (like RxJS).
In this case, we created a simple Subject class with methods to add and delete Observer class objects from the subscriber collection. In addition, a fire method is provided to propagate any changes in Subject class objects to subscribers. On the other hand, the Observer class has its own internal state, as well as a way to update the internal state based on changes propagated by Subject Subscription.

State mode

It is a behavioral design pattern that allows an object to change its behavior according to changes in its internal state. The object returned by the state pattern class seems to change its class. It provides state-specific logic for a limited set of objects, where each object type represents a specific state.
We will take traffic lights as an example to understand this model. The TrafficLight class changes the returned object based on its internal state (objects of the Red, Yellow or Green classes).

Strategic model

It is a behavioral design pattern that allows encapsulation of alternative algorithms for specific tasks. It defines a series of algorithms and encapsulates them in a runtime interchangeable manner without the need for client intervention or knowledge.
In the following example, we create a Commute class to encapsulate all possible commuting policies. Then, we define three strategies: Bus, Personal Car and Taxi. Using this pattern, we can replace the implementation with the travel ing method of the Commute object at runtime.

Template Method Patterns

This is a behavioral design pattern, based on the implementation of a framework or operation that defines the algorithm, but defers some steps to subclasses. It allows subclasses to redefine some steps of the algorithm without changing its external structure.
In this case, we have a template class Employee, which partially implements the work method. The way subclasses implement responsibilities is to make them work as a whole. Then, we create two subclasses, Developer and Tester, which extend the template class and implement the required methods.

summary

Design patterns are essential to software engineering and are very helpful for us to solve common problems. But it's a very broad topic, and it's impossible to cover all of them in a short article. So I choose to talk briefly and concisely about what I think is very convenient when writing modern JavaScript. For further understanding, I suggest that you read these books:
Design Patterns: The Foundation of Reusable Object-Oriented Software Authors: Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides
JavaScript Design Patterns Author: Addy Osmani
JavaScript Patterns, by Stoyan Stefanov
JavaScript Mode Author: Stoyan Stefanov

Look at it later

  • Point praise, so that more people can see this content (collecting no praise, are playing hooligans - -)
  • Pay attention to the public number "New Front-end Community" and enjoy the first article experience! Every week we focus on tackling a front-end technical difficulty.

Keywords: Javascript network JQuery calculator

Added by Skoalbasher on Sat, 10 Aug 2019 17:14:13 +0300