Writing in the front

Many things in life do more skilled later can often bear some routines, using routines can make things easier to deal with. A design pattern is a routine for writing code, like a swordsman’s sword score, which is a template for code.

Even those of you who have never seen design patterns must have been exposed to or inadvertently used some design patterns in your work. Because the principle of the code is the same, the implementation of a requirement may naturally follow the best pattern.

These patterns have been summarized by others, and can be used directly. When you read the source code of open source libraries, you will find that these libraries make extensive use of design patterns.

series

Simple javaScript design patterns

This series of content is mainly based on the understanding and summary of the book JavaScript Design Pattern by Zhang Rongming (a total of 5 chapters). Because I have my own code implementation in this book, and use part of the new syntax, I have abandoned some tedious content, and even have to correct the “wrong” code in the book. So if I find that I understand wrong, code write wrong place trouble be sure to point out! Thank you very much!

Front knowledge

Master basic javaScript syntax, and have a deep understanding of JS principles (especially prototype chain).

Structural design pattern

Structural design patterns focus on how to combine classes or objects into larger, more complex structures to simplify design.

First, appearance mode

Provides a higher level unified interface for a complex set of subsystems that makes access to subsystem interfaces easier. In JS, it is sometimes used to encapsulate the compatibility of the underlying structure to simplify the use of users.

To put it simply, facade mode is a secondary encapsulation of a series of complex interfaces to hide the details and simplify usage.

Binding elements to click events, for example, requires different apis in different browsers, and encapsulates a unified binding event method with a facade.

function addEvent(dom, eventType, fn) {
  if(dom.addEventListener) {
    // for browsers that support the DOMe2 level practice handler addEventListener method
    dom.addEventListener(eventType, fn)

  } else if (dom.attachEvent) {
    // For browsers that do not support addEventListener but support attachEvent
    dom.attachEvent('on' + eventType, fn)

  } else {
    // For browsers that do not support addEventListener or attachEvent, but do support on + event names
    dom['on' + eventType] = fn
  }
}
Copy the code

In many code bases, multiple functions are encapsulated by facade mode to simplify the underlying operation methods.

Jquery, for example, greatly simplifies DOM manipulation, with a selector method that encapsulates the selection of various DOM elements.

$('#testDom').html('test')
Copy the code

Second, adapter mode

Converting an interface (method or property) of one class (object) into another to meet user needs is a way to solve interface incompatibilities between classes (object) through adapters.

The so-called adapter is incompatible with the two sides together, for example, the triangle plug mobile phone charger can not be used in the two socket, if the middle provides an adapter can be used.

Scenario 1: Library adapter

You originally defined and used your own library in your project

/ / define
const MytTools = {
    setInnerHtml(id, html) {
      const $dom = document.getElementById(id)
      $dom.innerHTML = html
   }
   // Other methods
}

/ / use
MytTools.setInnerHtml('testDom'.'hello')
Copy the code

With the development of the project, we want to introduce jQuery to replace the original MytTools to improve the maintainability and compatibility.

// Write the adapter
MytTools.setInnerHtml = function(id, html) {$(The '#' + id).html(html)
}

Copy the code

As a result, new requirements are written using jQuery directly, older code is written using jQuery through the adapter, and the entire project is replaced with jQuery.

Scenario 2: Parameter adapter

When a function has many arguments, it is usually passed in as an object

function fn(name, age, color, size, price) {
// do something
}

function fn1(obj) {
  /* obj.name obj.age obj.color obj.size obj.price */
  // do something
}
Copy the code

However, the call may not know whether the object parameters passed in are complete, whether some necessary parameters are passed in, some parameters have default values, etc. In this case, you can use the parameter adapter.

function fn(obj) {
  // Parameter adapter
  const _adapter = {
    name: 'King Dog egg'.age: 18.color: 'red'.size: '18cm'.price: 100
  }

  for(key in _adapter) {
    _adapter[key] = obj[key] || _adapter[key]
  }

  console.log(_adapter.age)
}

// Pass in an empty object call
fn({}) / / 18
Copy the code

Using ES6, you can do this much better with structures and default parameters, and you don’t even need to pass in empty objects.

function fn({
    name = 'King Dog egg',
    age = 18,
    color = 'red',
    size = '18cm',
    price = 100
  } = {}) {

  console.log(age)
}

// Call without arguments
fn() / / 18
Copy the code
Scenario 3: Data adapter

A derivative of a parameter adapter that ADAPTS data structures with poor semantics.

// Data semantics are not friendly for arrays where each member represents a different meaning
const dataArr = ['King Dog egg'.18.'red'.'18cm'.100]

// Data adapter
function arrToObjAdapter(arr) {
  return {
    name = arr[0],
    age = arr[1],
    color = arr[2],
    size = arr[3],
    price = arr[4]}}// adapt
const dataObj = arrToObjAdapter(dataArr)
Copy the code

Sometimes the return field of the interface back end is changed and the front end is no longer matched. Adapters can also be used to ensure compatibility of the old code on the front end page.

// For example, the backend changes one of the name fields returned by the original data to title

/ / adapter
function resultAdapter(result) {
  return Object.assign({
    name: result.title
  }, result)
}

const data = resultAdapter({ title: 'King Dog egg' })
console.log(data.name) / / the dog egg king
Copy the code

In the traditional design pattern, the adapter pattern is often adapted to the incompatible problem of two class interfaces, while in JS, the application scope of the adapter is wider, such as adaptation of two code libraries, adaptation of front and back end data, and so on.

3. Agency mode

Since one object cannot directly refer to another, a proxy object acts as an intermediary between the two objects.

When two systems cannot communicate due to cross-domain restrictions, they can find a proxy object to achieve this.

Img tags

The IMG tag can send one-way GET requests to servers in other domains through the SRC attribute.

One-way means that the response data is not received, so it can be used to implement some requirements that only need to be sent without knowing the result, such as buried statistics on the page.

/ / points
const Recording = (function() {
  // Cache images
  const _img = new Image()

  // Return the buried point function
  return function() {
    // Request an address
    const url = 'http://www.test.com/pageView.png'

    // Send the request
    _img.src = url;
  }
})()

/ / use
Recording()
Copy the code
Jsonp with proxy templates

Jsonp’s principle is simply to allow cross-domain requests through script tags and to execute requested JS code features.

The front-end defines callback methods that receive returned data, and the back-end passes the returned data to the callback function.

const script = document.createElement('script');
script.src = 'http://www.test.com/listData';
document.head.appendChild(script);

// For example, the front-end defines the callback to receive data as a getData function, and the back-end defines the data as a getData() function
function getData(res) {
  console.log(res);
}
Copy the code

Reference: juejin. Cn/post / 684490…

At present, cross-domain development is basically solved by back-end CORS. Jsonp and proxy templates in the book are rarely used and will not be described again.

4. Decorator mode

Without changing the original object, extend it by wrapping it (adding attributes or methods) so that the original object can meet the user’s more complex needs.

It is often used to add new functions on the basis of original functions without modifying the original code.

For example, add new functions to the click events of a batch of existing elements, some of which are bound to the click events and some of which are not, and the internal functions of each element event are also different. If it is troublesome to add events one by one, you can define a decorator to achieve the purpose.

/ / decorator
function decorator(id, fn) {
  const $element = document.getElementById(id)

  // If the element originally had a click event
  if(typeof $element.onclick === 'function') {
    // Cache existing event functions
    const oldFun = $element.onclick

    // Define a new event function for the element
    $element.onclick = function() {
      // Execute the original event function
      oldFun()
      // Execute the element new event function
      fn()
    }
  } else {
    // Bind the event function directly
    $element.onclick = fn
  }
}
Copy the code
/ / use
decorator('dom1'.function() {
  console.log('Dom1 new features')
})

decorator('dom2'.function() {
  console.log('New dom2 features')})Copy the code

The decorator pattern is the addition of properties and methods to an existing object

Bridge mode

The system has been decoupled while varying along multiple dimensions without increasing its complexity.

Element events are decoupled from business logic

For example, make a mouse-over effect on the page, change the font color and background color

const $divs = document.getElementsByTagName('div')

$divs[0].addEventListener('mouseover', function() {
  this.style.color = 'red'
  this.style.background = 'blue'
})

$divs[0].addEventListener('mouseout', function() {
  this.style.color = '#000'
  this.style.background = '#fff'
})
Copy the code

This fulfils the requirement, but the events on this element (mouse in/out) are highly coupled to the business (changing colors). And this color change effect may be used by many other elements as well.

You can use the bridge pattern to decouple business from events.

Function changeColor(dom, fontColor, bgColor) { dom.style.color = fontColor dom.style.background = bgColor } const $divs = document.getElementsByTagName('div') $divs[0].addEventListener('mouseover', function() { changeColor(this, 'red', 'blue') }) $divs[0].addEventListener('mouseout', function() { changeColor(this, '#000', '#fff') })Copy the code

At this point, the business function can be used for different color requirements of different elements, and if the requirements change the business, only the business function needs to be modified, not in the callback of each event.

Decoupling when creating multivariate objects

For example, in the game, the character and a ball are different objects, but their movement method is the same, are x,y coordinates change; Characters and monsters speak different languages, but the way they do it is the same.

For such multivariate objects, their individual units can be abstracted, each of which can be bridged together when entities are created.

Function Move(x, x) y) { this.x = x this.y = y } Move.prototype.run = function() { console.log(`move to x:${this.x}, Y :${this.y} ')} function Speak(wd) {this.wd = wd} speak.prototype. Say = function() {console.log(this.wd)} // Create a character class, Function Person(x, y, wd) {this.move = new move (x, Y) this.speak = new speak (wd)} person.prototype.init = function() {this.move.run() this.speak.say()} Function Ball(x, y) {this.move = new move (x, y)} const p = new Person(10, 15, 'hello ') p.init() //move to x:10, Y: hi 15Copy the code

There is something similar to the creator pattern, but the difference I understand is that the main business of the creator pattern is to create, whereas the bridge pattern is mainly to decouple the structures.

That is, creator mode is the creation method used because you want to create objects that are complex, in order to create such an object. The bridge mode is to decouple the abstraction from the structure, so that the implementation layer and the abstraction can change independently.

Six, combination mode

Also known as the partial-whole pattern, objects are grouped into a tree structure to represent a partial-whole hierarchy. The composite pattern makes the use of single objects and composite objects consistent.

In simple terms, this is to create a tree structure in which each level (from the smallest child node to the outermost whole tree) has consistency of operations and data structure.

Each node is an independent entity, which does not affect each other, but can be combined to express a complex structure.

Writing HTML page structures is an example of a composite pattern, such as this DOM structure.

<div class="container" id="div1">
  <div class="header">
    <span>The title</span>
  </div>
  <p id="p1">A piece of writing</p>
</div>
Copy the code

It might look something like this in the virtual DOM.

const vdom = {
  tag: 'div'.attr: {
    className: 'container'.id: 'div1'
  },
  children: [{tag: 'div'.attr: {
        className: 'header'
      },
      children: [{tag: 'span'.attr: {},
          children: ['title']}]}, {tag: 'p'.attr: {
        id: 'p1'
      },
      children: ['A paragraph']]}}Copy the code

The outer Container node and its internal SPAN nodes are DOM, maintaining the consistency of properties and operation methods (you can add attributes or perform DOM operations like appendChild), and implement complex structures through composition.

This pattern is not common in writing business code, and you can see it in vDOM or in UI component libraries, such as navigation menus in elementUI, tree menus, and form components.

Seven, enjoy yuan mode

The use of sharing technology effectively supports a large number of fine-grained objects, avoiding the unnecessary overhead of having the same content between objects.

The purpose of the share pattern is to share memory to improve performance by extracting common data and methods.

It is similar to 50 people who drive their private cars to work, but with the same destination, they take buses together, thus saving the use of resources.

For example, in a list, each LI is bound to a click event.

<ul id="list">
  <li>a1</li>
  <li>a2</li>
  <li>a3</li>
  <li>a4</li>
  <li>a5</li>
</ul>
Copy the code

However, the logic of these events is similar, so you can bind an event to UL only once, using the event bubbling mechanism to broker event processing due to UL.

const $list = document.getElementById('list')

$list.addEventListener('click'.function(e) {
  console.log(e.target.innerHTML)
})
Copy the code

Instead of binding events for each item in the list, which need to be re-bound when the list turns pages and updates, events are only bound once. All lists share events through proxy mode, which conforms to the idea of both proxy mode and share mode.

When performance and memory consumption have little impact on program execution, imposing the free element pattern and introducing complex code logic is not worth the cost.