The purpose of improving code quality

Program ape’s job is to write code, write high-quality code should be our pursuit and requirements for themselves, because:

  1. High quality code often means fewer bugs and better modularity, which is the foundation of our scalability and reusability
  2. Higher quality code also means better writing, better naming, and better maintenance

What code counts as good quality

How to define “good” code quality, there are many standards in the industry, this article believes that good code should have the following characteristics:

  1. Clean code, such as indentation and so on, and there are now many tools that can solve this problem automatically, such as ESLint.
  2. The structure is neat, there is no long structure, the function is split reasonably, there will not be a function of thousands of lines, there will not be dozens ofif... else. This requires that the person writing the code have some optimization experience, and this article describes several patterns to optimize these situations.
  3. It’s easy to read, you don’t get a bunch of thema,b,cThe naming, instead, should be as semantic as possible, the names of the variables and the names of the functions should be as meaningful as possible, and the best thing is to make your code a comment so that people can see what you’re doing.

The design patterns introduced in this article mainly include policy/state pattern, appearance pattern, iterator pattern and memo pattern.

Policy/state patterns

Basic structure of policy pattern

Let’s say we need to make a calculator that supports addition, subtraction, multiplication and division. In order to determine which operation the user needs to perform, we need four if… Else, if more operations are supported, then if… Else would be longer, not good to read, not elegant to look at. Therefore, we can use the strategy mode to optimize as follows:

function calculator(type, a, b) {
  const strategy = {
    add: function(a, b) {
      return a + b;
    },
    minus: function(a, b) {
      return a - b;
    },
    division: function(a, b) {
      return a / b;
    },
    times: function(a, b) {
      returna * b; }}return strategy[type](a, b);
}

/ / when used
calculator('add'.1.1);
Copy the code

In the code above we replace multiple if… Else, all the operations that we need correspond to a property in this object, and the name of the property corresponds to the type that we passed in, so we can just use that property name to get the corresponding operation.

State mode basic structure

The state mode is similar to the policy mode. There is also an object to store some policies, but there is also a variable to store the current state. We can obtain specific operations according to the current state:

function stateFactor(state) {
  const stateObj = {
    status: ' '.state: {
      state1: function(){},
      state2: function(){},},run: function() {
      return this.state[this.status];
    }
  }
  
  stateObj.status = state;
  return stateObj;
}

/ / when used
stateFactor('state1').run();
Copy the code

if… Else changes the behavior of the code according to different conditions, while policy mode and state mode can change the behavior according to different policies or states passed in, so we can use these two modes instead of if… The else.

Example: Access permissions

The requirement for this example is that our page needs to render different content for different characters, if we use if… Else:

// There are three modules to display, different roles should see different modules
function showPart1() {}
function showPart2() {}
function showPart3() {}

// Get the role of the current user and decide which parts to display
axios.get('xxx').then((role) = > {
  if(role === 'boss'){
    showPart1();
    showPart2();
    showPart3();
  } else if(role === 'manager') {
    showPart1();
    showPart2();
  } else if(role === 'staff') { showPart3(); }});Copy the code

In the code above we get the role of the current user through an API request, and then a bunch of if… Else to determine which modules should be displayed, if there are many roles, here’s if… Else can be very long, we can try using state mode optimization:

// Wrap the characters in a ShowController class
function ShowController() {
  this.role = ' ';
  this.roleMap = {
    boss: function() {
      showPart1();
      showPart2();
      showPart3();
    },
    manager: function() {
      showPart1();
    	showPart2();
    },
    staff: function() { showPart3(); }}}// Add an instance method show on ShowController to display different content based on the role
ShowController.prototype.show = function() {
  axios.get('xxx').then((role) = > {
    this.role = role;
    this.roleMap[this.role]();
  });
}

/ / when used
new ShowController().show();
Copy the code

In the above code we have overwritten the access module with a state mode, removing the if… Else, and the display of different roles are encapsulated in roleMap, later to add or subtract will be much easier.

Example: Compound motion

The requirement for this example is that we now have a ball, and we need to control its movement, and it can move up, down, left, or left, or right, or some combination of that. If we also use if… Else, this is always big:

// Start with the basic movements in the four directions
function moveUp() {}
function moveDown() {}
function moveLeft() {}
function moveRight() {}

// A specific movement method can take one or two arguments, one for the basic operation, and two arguments for the upper left and lower right operations
function move(. args) {
  if(args.length === 1) {
    if(args[0= = ='up') {
      moveUp();
    } else if(args[0= = ='down') {
      moveDown();        
    } else if(args[0= = ='left') {
      moveLeft();        
    } else if(args[0= = ='right') { moveRight(); }}else {
    if(args[0= = ='left' && args[1= = ='up') {
      moveLeft();
      moveUp();
    } else if(args[0= = ='right' && args[1= = ='down') {
      moveRight();
      moveDown();
    }
    // If...}}Copy the code

You can see here if… Else let’s use strategy mode to optimize:

// Create a movement control class
function MoveController() {
  this.status = [];
  this.moveHanders = {
    // Write the corresponding method for each instruction
    up: moveUp,
    dowm: moveDown,
    left: moveLeft,
    right: moveRight
  }
}

// MoveController adds an instance method to trigger the motion
MoveController.prototype.run = function(. args) {
  this.status = args;
  this.status.forEach((move) = > {
    this.moveHanders[move]();
  });
}

/ / when used
new MoveController().run('left'.'up')
Copy the code

In the above code, we also encapsulate all the policies in moveHanders, and then execute the specific policies through the methods passed in by the instance method Run.

The appearance model

The basic structure

When we design a module, the methods inside can be designed in detail, but when exposed to external use, it is not necessary to directly expose these small interfaces. External users may need to combine some interfaces to achieve a certain function, and when exposed, we can actually organize this well. This is like a menu in a restaurant, with many dishes. Users can order dishes one by one or directly order a set meal. Appearance mode provides an organized set meal similar to this:

function model1() {}

function model2() {}

// A higher level interface can be provided to combine Model1 and Model2 for external use
function use() {
  model2(model1());
}
Copy the code

Example: Common interface encapsulation

The appearance mode is very common. Many modules are very complex internally, but there may be one or two external interfaces. We do not need to know the complex internal details, just call the unified high-level interface, such as the following TAB module:

// A tabbed class that may have multiple submodules
function Tab() {}

Tab.prototype.renderHTML = function() {}    // Render page submodule
Tab.prototype.bindEvent = function() {}    // Bind the event submodule
Tab.prototype.loadCss = function() {}    // Load a submodule of the style

// There is no need to expose the above specific submodules, just a high-level interface
Tab.prototype.init = function(config) {
  this.loadCss();
  this.renderHTML();
  this.bindEvent();
}
Copy the code

RenderHTML, bindEvent, loadCss can also be exposed, but external users may not care about these details, just need to give a unified high-level interface, which is equivalent to changing the appearance of the exposed. That’s why it’s called appearance mode.

Example: Method encapsulation

This is also a common example of encapsulating similar functionality into a method rather than writing it all over again. Back in the days when IE was dominant, we needed to do a lot of compatibility work. Just one bound event had addEventListener, attachEvent, onClick, etc. To avoid doing these checks every time, we could encapsulate them into a method:

function addEvent(dom, type, fn) {
  if(dom.addEventListener) {
    return dom.addEventListener(type, fn, false);
  } else if(dom.attachEvent) {
    return dom.attachEvent("on" + type, fn);
  } else {
    dom["on"+ type] = fn; }}Copy the code

We then expose addEvent for external use, which we often do in actual coding, but we may not realize that this is a facade mode.

Iterator pattern

The basic structure

Array forEach is an application of the iterator pattern. We can also implement a similar function:

function Iterator(items) {
  this.items = items;
}

Iterator.prototype.dealEach = function(fn) {
  for(let i = 0; i < this.items.length; i++) {
    fn(this.items[i], i); }}Copy the code

In the above code we create a new iterator class. The constructor receives an array, and the instance method dealEach receives a callback that executes on each item on the instance.

Example: Data iterator

Find takes a test function and returns the first data that matches that test function. This example extends this function to return all items that match the test function, and can also take two parameters, the first parameter is the attribute name and the second parameter is the value, and also return all items that match the value:

// The factory mode encapsulates the outer layer and calls it without writing new
function iteratorFactory(data) {
  function Iterator(data) {
    this.data = data;
  }
  
  Iterator.prototype.findAll = function(handler, value) {
    const result = [];
    let handlerFn;
    // If the first argument is a function, use it directly
    // If it is not a function, it is the attribute name, giving a default function for comparison
    if(typeof handler === 'function') {
      handlerFn = handler;
    } else {
      handlerFn = function(item) {
        if(item[handler] === value) {
          return true;
        }
        
        return false; }}// For each item in the loop, insert the items that match the result into the result array
    for(let i = 0; i < this.data.length; i++) {
      const item = this.data[i];
      const res = handlerFn(item);
      if(res) {
        result.push(item)
      }
    }
    
    return result;
  }
  
  return new Iterator(data);
}

// Write a data test
const data = [{num: 1}, {num: 2}, {num: 3}];
iteratorFactory(data).findAll('num'.2);    // [{num: 2}]
iteratorFactory(data).findAll(item= > item.num >= 2); // [{num: 2}, {num: 3}]
Copy the code

The above code extends its functionality by encapsulating an iterator like the array find, which is ideal for handling the large amount of structurally similar data returned by the API.

Memo mode

The basic structure

The memo mode is similar to the cache function that is often used by JS. It records a state internally, that is, the cache. When we access the cache again, we can directly retrieve the cached data:

function memo() {
  const cache = {};
  
  return function(arg) {
    if(cache[arg]) {
      return cache[arg];
    } else {
      // If there is no cache, execute the method first and get the result res
      // Then write res to the cache
      cache[arg] = res;
      returnres; }}Copy the code

Example: article caching

This example is more common in the actual project, the user every point into a new article to request data from the API, if he points into the same article again next time, we may want to use last time requested data directly, and not to request again, this time can use our memo mode, directly have to do is take the above structure to use:

function pageCache(pageId) {
  const cache = {};
  
  return function(pageId) {
    // To keep the return type consistent, we all return a Promise
    if(cache[pageId]) {
      return Promise.resolve(cache[pageId]);
    } else {
      return axios.get(pageId).then((data) = > {
        cache[pageId] = data;
        returndata; })}}}Copy the code

The above code uses the memo pattern to solve this problem, but the code is relatively simple, the actual project requirements may be more complex, but the idea is still a reference.

Example: Forward and backward functions

So the requirement for this example is, we need to make a DIV that can be moved, and the user can move that DIV around, but sometimes he’s misbehaving or he’s going to change his mind, and he wants to move that DIV back, so he wants to go back to the previous state, so there’s a need for the back state, and of course there’s a need for the paired forward state. Similar requirements can be implemented with the memo pattern:

function moveDiv() {
  this.states = [];       // An array records all states
  this.currentState = 0;  // a variable records the current state position
}

// Move the method to record the status of each move
moveDiv.prototype.move = function(type, num) {
  changeDiv(type, num);       // Pseudocode to move a DIV, not implemented here
  
  // Record this operation in states
  this.states.push({type,num});
  this.currentState = this.states.length - 1;   // Change the current state pointer
}

// Forward method, fetch state execution
moveDiv.prototype.forward = function() {
  // If the current state is not the last state
  if(this.currentState < this.states.length - 1) {
    // Retrieve the forward state
    this.currentState++;
    const state = this.states[this.currentState];
    
    // Execute the status positionchangeDiv(state.type, state.num); }}// The fallback method is similar
moveDiv.prototype.back = function() {
  // If the current state is not the first state
  if(this.currentState > 0) {
    // Retrieve the backward state
    this.currentState--;
    const state = this.states[this.currentState];
    
    // Execute the status positionchangeDiv(state.type, state.num); }}Copy the code

This code keeps track of all the states that the user has acted on through an array, allowing the user to move back and forth between the states at any time.

conclusion

The policy/state, appearance, iterator, and memo patterns are well understood and are common enough to reduce redundant code and improve the quality of our code.

  1. The strategy patternBy putting ourifThere are fewer strategies for rewriting conditions into stripsif... elseIt looks cleaner and is easier to expand.The state patternwithThe strategy patternIt’s similar, but with an extra state that you can use to select specific strategies.
  2. The appearance modelPerhaps we have inadvertently used this to encapsulate some of the module’s internal logic inside a higher-level interface, or to encapsulate similar operations inside a method to make external calls easier.
  3. Iterator patternThere are many implementations on JS arrays, and we can also emulate them to do some data processing, especially suitable for handling the large amount of structured data from apis.
  4. Memo modeA cache object is used to record the status of previously acquired data or operations, which can later be used to speed up access or roll back state.
  5. Again, design patterns are about understanding ideas, and they can be implemented in many different ways.

This is the final article on design patterns. The first three are:

(500+ upvotes!) Don’t know how to encapsulate code? Take a look at these design patterns!

(100+ upvotes!) Framework source code to improve the extensibility of the design pattern

Not sure how to improve code reuse? Take a look at these design patterns

At the end of this article, thank you for your precious time to read this article. If this article gives you a little help or inspiration, please do not spare your thumbs up and GitHub stars. Your support is the motivation of the author’s continuous creation.

The material for this article comes fromNetease Senior Front-end Development Engineer Micro majorTang Lei’s design pattern course.

Welcome to follow my public numberThe big front end of the attackThe first time to obtain high quality original ~

“Front-end Advanced Knowledge” series:Juejin. Cn/post / 684490…

“Front-end advanced knowledge” series article source code GitHub address:Github.com/dennis-jian…