🐛 nested caterpillar

I’m sure you’ve seen code like this:

if(condition1 === A1){ if(condition2 === A2){ ... }else if(condition2 === B2){ ... }else if(condition2 === C2){ ... }else{ ... } }esle if(condition1 === B1){ ... . . }else if(condition1 === C1){ ... . . }else if(condition1 === D1){ ... . . }else{ ... }Copy the code

To be honest, it’s not that this code is bad, but it makes me sick every time I see it.

It feels like a caterpillar…

In order to express this image, Bengua invites the soul painter to guard Antony for a diagram, salute!! ( ̄)  ̄

* Photo source: Guardian Anthony, reproduced without permission.

Writing like this, always accompanied by a variety of logical judgment, implicit input, output, really do not dare to move it, worried that it directly “dead” to you!

🌴 Responsibility chain bamboo

The camera turns to [chain of Responsibility], which is one of the 23 design patterns. It belongs to behavioral pattern and focuses on the interaction and communication between objects.

Parameter input to an initial function, if the current function condition does not meet, then passed to the next function for processing, satisfies the stop, does not meet the pass, so one by one backwards, until the condition is met or the end of the pass.

A meta-function is like a piece of bamboo, independent detachable, then arbitrary assembly;

Without further ado, the code to implement it looks something like this:

function A1(condition1){ chainA2.next(chainB2).next(chainC2); return condition1 === A1 ? chainA2.setParam(condition2) : 'doNext' } function B1(condition1){ return condition1 === B1 ? . : 'doNext' } function C1(condition1){ return condition1 === C1 ? . : 'doNext' } function D1(condition1){ return condition1 === D1 ? . : 'doNext' } ... function A2(condition2){ return condition2 === A2 ? . : 'doNext' } function B2(condition2){ return condition2 === B2 ? . : 'doNext' } function C2(condition2){ return condition2 === C2 ? . : 'doNext' } chainA1.next(chainB1).next(chainC1).next(chainD1) chainA1.setParam(condition1)Copy the code

Is it like bamboo in the sense of the whole? Each section (function input and output) is particularly clear. The point is, it’s decoupled and super easy to assemble

* Photo source: Guardian Anthony, reproduced without permission.

The core, Chain generation code is as follows:

The Chain function is a high-level function, and its input is a function. Here we add the next and setParam properties to it by way of prototype chain. The next input is also fn, which is used to set the next handler, and setParam is used to pass the original input;

var Chain = function( fn ){
  this.fn = fn;
  this.successor = null;
};
Chain.prototype.next = function( successor ){
  return this.successor = successor;
};
Chain.prototype.setParam = function(){
  var ret = this.fn.apply( this, arguments );
  if ( ret === 'doNext' ){
    return this.successor && this.successor.setParam.apply( this.successor, arguments );
  }
  return ret;
};
Copy the code

🍜 Functional features AOP

In fact, there is a more convenient way to create a chain of responsibility by taking advantage of JavaScript’s functional nature: AOP.

A simple understanding of AOP: Aspect Oriented Program (AOP) is the idea of dynamically cutting code into the specified methods and locations of classes.

The code is as follows:

/ Function interweave (AOP) * * * * @ param *} {fn * @ returns * / Function in the prototype. Before = Function (fn) {const self = this return function(... args) { const result = fn.apply(null, args) return self.call(null, result) } } Function.prototype.after = function(fn) { const self = this return function(... args) { const result = self.apply(null, args) return fn.call(null, result) } }Copy the code

Example call:

fn1 = step2.before(init).after(step3).after(step4)

//fn1 = init -> step2 -> step3 -> step4
Copy the code

We can specify the sequence of execution of the collocation function arbitrarily;

🥂 composeAOP

Remember the article, “Thanks to the compose function, my code 💩 is getting beautiful ~”? For example: compose, there are many ways to write it! We can implement this version of composeAOP using the before and after functions above

const composeAOP = function(... args) { const before = args.pop() const start = args.pop() if (args.length) { return args.reduce(function(f1, f2) { return f1.after(f2) }, start.before(before)) } return start.before(before) }Copy the code

For example, “compose” is used from right to left.

const compose = function(... args) { if (args.length) { return args.reverse().reduce(function(f1, f2) { return f1.after(f2) }) } } compose(step4,step3,step2,step1,init)("start")Copy the code

The reason for this is that it simulates the order in which functions are called layer by layer, like peeling an onion, from the outside in, right to left:

step4(step3(step2(step1(init(... Args))))) // The layers of parentheses resemble the skins of an onionCopy the code

If you prefer to go from left to right, change pop() to shift(), remove the reverse() layer, or change after to before…… Order problem, not good or bad, all by preference ~

🎯 pass ginseng problem!!

If you are willing to try the above code on the console, it is not difficult to find a serious pass-argument problem!! This issue actually exists in the Compose Optimized Mountain post, and there was some careful digging feedback.

function init(... args){ console.log(args) return [...args,"init"] } function step1(... args){ console.log(args) return [...args,"step1"] } function step2(... args){ console.log(args) return [...args,"step2"] } function step3(... args){ console.log(args) return [...args,"step3"] } compose(step3,step2,step1,init)("start")Copy the code

The dimensions of the args array increase as arguments are passed.

If we use flat(Infinity) to flatline the array, the passed argument will look like this:

One big problem with this is that you need to compare the order of arguments in the array! This is a headache because you may have to add or modify process parameters one day.

So, the expectation is to be able to pass objects instead, eliminating the constraint of passing arguments in order. Such as:

{start:"start",init:"init",step1:"step1"...... }Copy the code

Just try it:

function init(... args){ console.log(JSON.stringify(args)) return {args:args,init:"init"} } function step1(... args){ console.log(JSON.stringify(args)) return {args:args,step1:"step1"} } function step2(... args){ console.log(JSON.stringify(args)) return {args:args,step2:"step2"} } function step3(... args){ console.log(JSON.stringify(args)) return {args:args,step3:"step3"} } compose(step3,step2,step1,init)("start")Copy the code

Get:

Obviously that’s not what we want, so we have to keep printing to find patterns:

Wow ~

In step3, to obtain step1, two.args[0] are needed;

To obtain step1 in step2, only 1.args[0] is needed;

We can basically deduce: to obtain the parameters of the first N steps, only need N.args[0]

So, we can try to write a getCountStepAttr() function that takes the NTH step in a function step, by calling the object property!

Come on, spread your wings

Function getCountStepAttr(args,N){let resObj = args[0] for(let I =0; i<N; i++){ resObj = resObj.args[0] } return resObj }Copy the code

You can test it directly:

🖖 complete code

Paste the complete code, you can copy in the console to play a look, this melon believe that you must have a harvest!!

Function.prototype.after = function(fn) { const self = this return function(... args) { let result = self.apply(null, args) return fn.call(null,result) } } const compose = function(... args) { if (args.length) { return args.reverse().reduce(function(f1, }} const getCountStepAttr = function(args,N){// getCountStepAttr = function(args,N); N = N -1 let resObj = args[0] for(let i =0; i<N; i++){ resObj = resObj.args[0] } return resObj } function init(... Args){console.log(" [calling the original pass in init] : ",getCountStepAttr(args,1)) return {args:args,init1:"init1",init:"init"} } function step1(... args){ return {args:args,step1:"step1"} } function step2(... args){ return {args:args,step2:"param-step2",step2Add:"param-step2-add"} } function step3(... Args){console.log(" [call step2 in step3] : ",getCountStepAttr(args,1).step2,getCountStepAttr(args,1).step2add) console.log(" ",getCountStepAttr(args,3). Init,getCountStepAttr(args,3).init1) console.log(" ",getCountStepAttr(args,4)) return {args:args,step3:"step3"} } compose(step3,step2,step1,init)("start")Copy the code

🐵 Summary outlook

What are we talking about?

It’s still those five big words: functional programming.

We encapsulate the imperative code in the process into simple pure functions and combine them into a variety of rich functions.

In the process, you can disassemble, add to, or refactor the design at will, and really don’t worry too much about hidden logic errors or coupling caused by complex business difficult to tease out!

We use the input and output of functions to express the mapping relationship, use the function name to express the function implementation, use the passing of parameters to express the business logic, use the closed scope environment to construct clean code ~

Of course, you probably have a lot of good ideas, and the code is still a long way from being clean! The mountain, jingxingxingstop, although not to the heart yearning. Besides, can “to” really not necessarily!

👍👍👍 Writing is not easy, thank you 👏 👍

Welcome to like, collect, comment ~

I am Anthony of nuggets, public account of the same name, output exposure input, technology insight life, goodbye ~