In JavaScript, there are too many chain pattern codes, as follows:
if_else:
if(...){ //TODO }else if(...){ //TODO }else{ //TODO }
switch:
switch(name){ case ...:{ //TODO break; } case ...:{ //TODO break; } default:{ //TODO } }
Question: Such as the above chain codes, what if we want to flatten them and chain them? As follows:
//fn1,f2,f3为处理函数 _if(fn1)._elseIf(fn2)._else(fn3);
Let’s try to implement it together.
Suppose now we have the following chain code:
if(name === 'Monkey'){ console.log('yes, I am Monkey'); }else if(name === 'Dorie'){ console.log('yes, I am Dorie'); }else{ console.log('sorry, over for ending!'); }
Okay, now we will "flatten it step by step" change".
In fact, looking at the above code, it is not difficult to find that the if...else format is actually a singly linked list in the data structure. Then, initially use JavaScript to implement a singly linked list, as follows:
var thens = []; thens.resolve = function(name){ for(var i = 0, len = this.length; i < len;i++){ if(this[i](name) !== 'next'){ break; } } } thens.push(f1, f2, f3);
Where f1, f2, f3 are judgment functions, and we assume that if f1, f2, f3 returns 'next', continue to search downwards, otherwise, stop searching downwards. As follows:
function f1(name){ if(name === 'Monkey'){ console.log('yes, I am Monkey'); }else{ return 'next'; } } function f2(name){ if(name === 'Dorie'){ console.log('yes, I am Dorie'); }else{ return 'next'; } } function f3(){ console.log('sorry, over for ending!'); }
Okay, this is the pattern of the linked list.
But, our ultimate goal is to achieve the following?
//fn1,f2,f3为处理函数 _if(fn1)._elseIf(fn2)._else(fn3);
You might say, wouldn’t it be nice to change the above code to the following? ! !
thens.push(f1).push(f2).push(f3).resolve();
But, JavaScript’s push method returns the new length of the array, not the array object.
So, Then we can only write a new add method, which has the same effect as push, but returns an array object. As follows:
thens.add = function(f){ if(typeof f === 'function'){ this.push(f); return this; } }
The test code is as follows:
var thens = []; thens.add = function(f){ if(typeof f === 'function'){ this.push(f); return this; } } thens.resolve = function(name){ for(var i = 0, len = this.length; i < len;i++){ if(this[i](name) !== 'next'){ break; } } } thens.add(f1).add(f2).add(f3).resolve();
However, this has a disadvantage. We bind the add and resolve methods to the global variable thens. You can't just copy and paste the method every time you create an array, so the refactored code is as follows:
function Slink(){ this.thens = []; this.thens.add = function(f){ if(typeof f === 'function'){ this.push(f); return this; } } this.thens.resolve = function(name){ for(var i = 0, len = this.length; i < len;i++){ if(this[i](name) !== 'next'){ break; } } } }
Obviously, public methods like add and resolve are not created every time they are instantiated. Scientifically, so, use prototype to continue deforming on the original basis, as follows:
function Slink(){ this.thens = []; } Slink.prototype = { add: function(f){ if(typeof f === 'function'){ this.thens.push(f); return this; } }, resolve: function(name){ for(var i = 0, len = this.thens.length; i < len; i++){ if(this.thens[i](name) !== 'next'){ break; } } } }
The test code is as follows:
var thens = new Slink(); thens.add(f1).add(f2).add(f3); thens.resolve();
Not bad, but like this, we fail every time It is a bit troublesome to manually create a new Slink, so we encapsulate the new Slink process into a function, just like jQuery, as follows:
function $go(f){ return new Slink(f); } function Slink(f){ this.thens = []; this.thens.push(f); } Slink.prototype = { add: function(f){ if(typeof f === 'function'){ this.thens.push(f); return this; } }, resolve: function(name){ for(var i = 0, len = this.thens.length; i < len; i++){ if(this.thens[i](name) !== 'next'){ break; } } } }
The test code is as follows:
$go(f1).add(f2).add(f3).resolve();
Okay, you’re done, now comes the syntax sugar problem. The code is as follows:
function _if(f){ return new Slink(f); } function Slink(f){ this.thens = []; this.thens.push(f); } Slink.prototype = { _elseIf: function(f){ if(typeof f === 'function'){ this.thens.push(f); return this; } }, _else: function(f){ return this._elseIf(f); }, resolve: function(name){ for(var i = 0, len = this.thens.length; i < len; i++){ if(this.thens[i](name) !== 'next'){ break; } } return this; } }
The test code is as follows:
_if(f1)._elseIf(f2)._else(f3).resolve();
Of course, except for using arrays In this way, you can also use closure to achieve a chain flattening effect, as follows:
var func = Function.prototype; func._else = func._elseIf = function(fn){ var _this = this; return function(){ var res = _this.apply(this,arguments); if(res==="next"){ //值为Boolean return fn.apply(this,arguments); } return res; } }
The test code is as follows:
function f1(name){ if(name === 'Monkey'){ console.log('yes, I am Monkey'); }else{ return 'next'; } } function f2(name){ if(name === 'Dorie'){ console.log('yes, I am Dorie'); }else{ return 'next'; } } function f3(){ console.log('sorry, over for ending!'); } f1._elseIf(f2)._else(f3)('Dorie');
What we discussed above are all synchronous processes. What if there is an asynchronous situation in the chained calling function?
What's the meaning? As follows:
function f1(name){ setTimeout(function(){ if(name === 'Monkey'){ console.log('yes, I am Monkey'); }else{ return 'next'; } }, 2000); } function f2(name){ if(name === 'Dorie'){ console.log('yes, I am Dorie'); }else{ return 'next'; } } function f3(){ console.log('sorry, over for ending!'); }
We use setTimeout to make f1 asynchronous. According to the logic of the above code, it should be judged whether to execute f2 after f1 is completely executed (including setTimeout execution), but is this really the case?
The test code is as follows:
_if(f1)._elseIf(f2)._else(f3).resolve();
The result of executing the code is that nothing is output.
Why?
Because JavaScript is single-threaded. See (here) for details
So how to solve it?
Since there is asynchronous code and the subsequent chain must be processed after the asynchronous code, then we wait for the asynchronous code to be executed before executing the subsequent chain, as follows:
function f1(name){ setTimeout(function(){ if(name === 'Monkey'){ console.log('yes, I am Monkey'); }else{ //处理后续链 this.resolve(name, 1);//1代表下一个需处理函数在数组中的位置 } }.bind(this), 2000); }
Okay , since in the function, we used this, which represents the Slink object, and changed the resolve method, it is necessary to fine-tune the Slink constructor and prototype chain, as follows:
function Slink(f){ this.thens = []; this.thens.push(f.bind(this)); } Slink.prototype = { _elseIf: function(f){ if(typeof f === 'function'){ this.thens.push(f.bind(this)); return this; } }, _else: function(f){ return this._elseIf(f.bind(this)); }, resolve: function(name, flag){ for(var i = flag, len = this.thens.length; i < len; i++){ if(this.thens[i](name) !== 'next'){ break; } } return this; } }
The test code is as follows:
function f1(name){ setTimeout(function(){ if(name === 'Monkey'){ console.log('yes, I am Monkey'); }else{ //处理后续链 this.resolve(name, 1);//1代表下一个需处理函数在数组中的位置 } }.bind(this), 2000); } function f2(name){ if(name === 'Dorie'){ console.log('yes, I am Dorie'); }else{ return 'next'; } } function f3(){ console.log('sorry, over for ending!'); } _if(f1)._elseIf(f2)._else(f3).resolve('',0);
Haha, if you know Promise, do you think it is so similar?
Yes, the purpose is the same, to achieve the purpose of flattening asynchronous code, but the code here is much simpler than Promise. For details about Promise, see (here).
The above is the detailed explanation of JavaScript chain structure serialization. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!