Es6 era is coming, I believe that will let a group of Java, C++ and other object-oriented language development foundation of the people, feel from the js world full of goodwill. Es6 allows developers to almost get out of prototype programming mode, making development more silky and smooth. Although most browsers don’t support ES6, most front-end and Node developers have enjoyed es6 era with the rapid development of Node and Babel. Object oriented has a lot of subtle design ideas, although I believe that we have used a lot of ideas JS framework, front-end such as Redux, back-end frameworks such as Express, KOA, etc., of course, there are many other excellent frameworks, but not related to our topic today. If you’ve ever worked with Redux or KOA, you’re probably familiar with the middleware. Although middleware is used differently in different frameworks, its implementation principle is generally the same. We find that as a middleware, no matter what capabilities it implements, one of its main functions is to enhance the capabilities of the target object. In the process of studying each big middleware, dimly see one of its familiar figure, that is adornment mode. Among many design patterns, decoration pattern is most widely used to enhance the ability of target objects. Most middleware implementations should draw lessons from this flexible design idea of decoration pattern. So, let’s start with decorator patterns, which we can’t help but mention a new annotation feature in the ES7 proposal (I prefer to call it annotations because it’s written like annotations in Java), such as the following class that defines addition and subtraction methods:

class MyClass {
  add(a, b){
    return a + b;
  }
  sub(a, b){
    returna - b; }}Copy the code

If we had a requirement to print the log before and after each add or sub call, such as ‘before operate’ and ‘after operate’, would we need to call console.log() before and after each call? In ES7, of course not, we just need to define the printing function we need, and then use the @ annotation, as follows:

// Annotate the function definitionlet log = (type) => {
    const logger = console;
    return(target, name, descriptor) => { const method = descriptor.value; descriptor.value = (... args) => { logger.info(`(${type}) before function execute: ${name}(${args}) = ?`);
            let ret = method.apply(target, args);
            logger.info(`(${type})after function execute: ${name}(${args}) = >${ret}`);
            returnret; }}} // Annotation calls class MyClass {@log("add")
  add(a, b){
    return a + b;
  }
  @log("sub")
  sub(a, b){
    returna - b; }}Copy the code

Add and sub are instantiated by the MyClass method. This is an es7 syntax that allows you to define annotations in a simple way. One function returns another function, and the parameters of the return function are target: the context of the class, name: the name of the target method, descriptor. This is for ES7, and the compiler support still looks a bit abstract, so let’s take a look at how regular ES5 objects can be enhanced using decorator patterns. Here’s the add function

function add(a, b){
	return a + b;
}
Copy the code

Now you need to enhance the ability of log and notify to print logs and send messages before calling. The code is as follows:

function logDecorator(target){
	var old = target;
	return function(){
		console.log("log before operate");
		var ret = old.apply(null,arguments);
		console.log(target.name,"The results.",ret,",log after operate");
		returnret; }}function notifyDecorator(target){
	var old = target;
	return function(){
		console.log("notify before operate");
		var ret = old.apply(null,arguments);
		console.log("finished, notify u");
		return ret;
	}
}
var add = logDecorator(notifyDecorator(add));
Copy the code

Var old = target; Var ret = old.apply(null,arguments); var ret = old.apply(null,arguments); Execute the original objective function call, at this time, or before or after, in need of the node for specific ability enhancement, is not very disappointed, why so simple? Sorry, it’s as simple as that, but this is the basic principle of the top middleware in every major framework. In the case of KOA, what if we needed to implement a simple log middleware?

module.exports = (opts = {}) => {
    var log = console.log;
    return async (ctx, next) => {
        log("before ",ctx.request.url, "...");
        await next();
        log("after ",ctx.request.url, "..."); }}Copy the code

Of course, we can make some filtering conditions in middleware, such as we only want custom logs for requests from non-static resources, etc. Koa and Express as a background framework, middleware is different in the implementation of routing, sounds a bit complex oh. In fact, in the case of KOA, we want to implement routing, ctx.request.url string parsing into different processing functions, can we have a basic routing function? So middleware is powerful, it’s actually very simple, it’s not contradictory. Now that middleware is defined, let’s see how to use it. For example, the middleware component is composed, and the middleware component is composed. The middleware component is composed in many different ways, but the principle is the same. A batch of middleware is added, stored in a list of functions, and then executed sequentially, with the return value of each function as the input parameter of the next function. Let’s take the middleware of KOA and Redux as an example. First, koA’s:

App.use (middleware);Copy the code

Koa – compose source code:

function compose (middleware) {
  if(! Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
  for (const fn of middleware) {
    if(typeof fn ! = ='function') throw new TypeError('Middleware must be composed of functions! ')
  }

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if(! fn)return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
Copy the code
letfn = compose(middlewares); fn(ctx)... ;Copy the code

First, join the middleware with app.use() and make a recursive call to the middleware middlewares list using the compose function above. Var old = target; var old = target; This is an updated version of this approach, and the next approach solves new middleware problems more elegantly, without the need for nested calls. In fact, js native provides a way to compose, which can solve this problem more gracefully. Redux uses this method, which is to use the reduce function. Let’s look at the redux processing method:

export default functioncompose(... funcs) {if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  returnfuncs.reduce((a, b) => (... args) => a(b(... args))) }Copy the code

Reduce plus ES6 is pleasing to the eye. If you are not comfortable with it, you can change it to ES5. Here is a simple test case for you to run.

function fun1(obj){
	console.log(1);
	obj.a=1;
	return obj;
}
function fun2(obj){
	console.log(2);
	obj.b=2;
	return obj;
}
let fn = compose(fun1,fun2);
fun({});
Copy the code

See what was the result of a call, this is just a small help understand the chestnuts, chestnuts are small, but has little show a muscle, the key is that we passthrough to the parameters in each middleware obj, can be an object, and can also be a function, in short, we can do whatever you want to enhance the ability of it. According to different purposes, middleware implementation mechanism will have some differences, koA and Redux actually have some obvious differences, interested in in-depth look, but all changes are the same. To this, middleware definition and call some of the core logic on the end, are personal some shallow, the level is limited, if there are fallacies, please point out!!