Play a JS chained call

Source: Internet
Author: User

Chaining calls we usually use a lot, such as $ (ele) in jquery. Show (). Find (), hide (), and so on, such as $http.get (URL) in Angularjs. Success (fn_s). Error (Fn_e). But this is already packaged chain call, we can only realize the convenience of chain call, but do not know how to form such a function chain principle is what.

With the popularity of chained calls, more and more programs are implemented. The most common is the way that jquery returns this directly, underscore in an optional way, and lodash lazy evaluation. Let's get to know each other and complete their demos one by one.

  from the simplest of beginnings , the direct return of this is the most common way, and is also the basis of all methods. We implement a simple chain operation class, first of all it has to have a field to preserve the result.

function A (num) {  this. Value = num | | 0;  // Do not pass the compared with his Test }

Then add a method to perform the operation and return this.

function (a) {this returnthisfunction(a) {thisreturn  this;}

Finally, in order to show the normal modification of two inherited methods.

function () {returnthisfunction() {returnthis. Value + ';}

For verification.

var New A (2); alert (A.add (1). Reduce (2))

This demo should be as simple as not to explain any code, we quickly came to the second one , that is, underscore in the use of chain. Underscore provides two ways of calling, _.foreach (arr, fn), _.map (arr, FN), and _.chain (arr). ForEach (FN).

Let's start with the previous invocation, because this is not about underscore, so we're simply implementing the Foreach and map functions, not the object but the array.

var _ =function(array, fn) {    Array.foreach (function(v, I, array {      fn.apply (v, [V, I, array])  };   function (Array, fn) {    return array.map (function(v, I, array) {        return  fn.apply (v, [V, I, array]);}    )  };

The above code is simple and calls directly to the ES5 array prototype method. The next question is, what do we do first to implement chained calls? We see that in the second invocation, all operations, whether foreach or map, are called on _.chain (arr), so _.chain (arr) should be returning an object that has the same method as _ on it, only implementing the upload parameter from 2 to 1, Because the original first parameter is always a copy of the parameters passed in _.chain.

Okay, OK. _.chain (arr) to return an object, what about the constructor of this object? We use a ready-made variable to hold this constructor, which is _. Functions are also objects, so when _ is changed from an object to a function, the original logic is not affected, and the function passes in an array and returns a new object. So the above code should be changed to this.

var_ =function(array) { This. _value =Array.prototype.slice.apply (Array); } _.foreach=function(Array, fn) {Array.foreach (function(V, I, array) {fn.apply (V, [V, I, array]);  })  }; _.map=function(Array, fn) {returnArray.map (function(V, I, array) {returnfn.apply (v, [V, I, array]);  })  }; _.chain=function(array) {return New_ (array); }

The new constructor has, but it produces the object in addition to _value is a blank, how do we have the original _ on the method of a little modification of the migration to the generated object? The code is as follows:

 for(varIinch_) {//first we have to traverse _    if(I!== ' chain ') {//and then we're going to remove chain_.prototype[i] = (function(i) {//all the other methods are treated and assigned to _.prototype.        return function() {//i is a global variable and we want to convert it to a local variable by a closure.          varargs = Array.prototype.slice.apply (arguments);//Take out the parameters of the new method, in fact, the FN oneArgs.unshift ( This. _value);//put the _value into the first bit of the parameter array          if(i = = = ' Map ') {//when the method is map, you need to modify the value of the _value             This. _value = _[i].apply ( This, args); }Else{//when the method is foreach, you do not need to modify the value of the _value_[i].apply ( This, args); }          return  This;    }}) (i); }  }

Finally we imitate underscore using value to return the current _value.

function () {    returnthis. _value;  }

For verification.

 var  a = [1, 2, 3 function   (v) {Console.log (  v);}) Alert (_.map (A,  function  (v) {return   ++V;})) Alert (_.chain (a). Map ( function  (v) { ++v;}). ForEach (function  (v) {console.log (v);}). Value ()) 

These are the simplified versions of chained calls used in underscore, which should not be difficult to understand. What is the most complicated, Lodash lazy call ? First, let me explain what an lazy call is, such as _.chain (arr). ForEach (FN). Map (FN). Value (), when executed to chain (arr), returns an object that starts polling when it executes to ForEach. After polling and returning this object, the execution of the map begins polling again, the object is returned after polling, and finally executes to value, returning the value of _value in the object. Each of these steps is independent, in turn. Instead of performing a polling operation when executing to foreach, the lazy call is to plug the operation into the queue, execute it to the map, and then plug the map operation into the queue. When will it be executed? When a particular action is plugged into the queue, all operations in the previous queue are executed, such as when value is called, and the execution of foreach, map, and value begins.

What are the benefits of lazy calling, and why is it better to plug a bunch of operations together? We see that the traditional chain operation is in this format, OBJ.JOB1 (). JOB2 (). JOB3 (), yes, the entire function chain is the job chain, if there is a simple need, such as continuous execution 100 times job1-3, then we will write 100 times, Or disconnect the entire chain 100 times with for. So the disadvantage of traditional chain operation is obvious, the function chain is job, there is no controller. And once the controller is added, such as the above requirements, we use a simple lazy call to achieve, that is Obj.loop (). JOB1 (). JOB2 (). JOB3 (). End (). Done (). Where loop is declared open 100 cycles, end is the end of the current cycle, done is the flag to start the task, how simple the code!

Now we implement the lazy chain call, because Lodash is underscore's powerful version, the general architecture is similar, and above already have underscore basic chain implementation, so we break away from Lodash and underscore of other code, Just implement a similar lazy call to the demo.

First we need to have a constructor that generates an object that can be called by a chain. As mentioned before, any controller or job call is to plug it into the task queue, then this constructor naturally has a queue property. With the queue, there must be an index indicating the currently executing task, so there is a queue index. So this is the constructor for the moment.

function Task () {    this. Queen= [];
this. queenindex = 0; }

If we are going to implement loop, then we have to have the total number of loops and the current loop number, and if one loop ends, where do we go back to the task queue? So there's a property to record where the loop starts. The final form of the constructor is this:

function Task () {    this. Queen = [];      this. queenindex = 0;     this. loopcount = 0;     this. loopindex = 0;     this. Loopstart = 0;  }

Now we are going to implement the controller and the job, as the example above says: Job (), loop (), end (), done (). They should all contain two forms, one is the original business logic, such as job business is do something, and loop control logic is to record loopcount and loopstart,end control logic is loopindex+ 1 and check Loopindex to see if it needs to go back to Loopstart's position to traverse again. The other pattern is that whatever the business logic is, the code that corresponds to the business logic is crammed into the task queue, which can be called the first form of the wrapper.

If our final invocation format is new Task (). Loop (). Job (). Done (), then the methods on the method chain must be wrappers, and these methods should naturally be placed on the Task.prototype, the first form of the way to go? Then put it on the task.prototype.__proto__. We write like this.

var_task_proto ={loop:function(num) { This. Loopstart = This. Queenindex;  This. Loopcount =num; }, Job:function(str) {console.log (str); }, End:function() {       This. loopindex++; if( This. Loopindex < This. Loopcount) {         This. Queenindex = This. Loopstart; }Else {         This. Loopindex = 0; }}, Done:function() {Console.log (' Done ');  }  }; task.prototype.__proto__= _task_proto;

The wrapper is then generated on the task.prototype in the traversal _task_proto, and each wrapper returns this for a chained call (see no, in fact, every kind of chained invocation is done this way)

 for  (var  I in   _task_proto) {( function   (i) { var  Raw = Task.prototype[i]; Task.prototype[i]  = function   () { this  .queen.push ({name:i, Fn:raw, args:arguments}); //  save specific implementation methods, names, and parameters to the task queue 
return this

Now that the problem has come, when do we start to perform specific tasks, and how to make the task of the orderly implementation and jump? At this point we are going to define a new method on Task.prototype, which is designed to control the execution of the task, because the task queue is executed sequentially and indexed by the index, which is similar to the iterator, and we define the new method called Next.

Task.prototype.next =function() {    varTask = This. queen[ This. Queenindex];//Take out a new taskTask.fn.apply ( This, Task.args);//execute the specific implementation method pointed to in the task and pass in the previously saved parameters    if(Task.name!== ' done ') {       This. queenindex++;  This. Next ();//if not done, task index +1 and call next again}Else {       This. Queen = [];  This. Queenindex = 0;//If you're done, empty the task queue and reset the task index    }  }

Added next, we need to add something on the done wrapper to get the task queue to start executing, modify the code that generated the wrapper before

   for(varIinch_task_proto) {    (function(i) {varRaw =Task.prototype[i]; Task.prototype[i]=function() {         This. Queen.push ({name:i, Fn:raw, args:arguments});//save specific implementation methods, names, and parameters to the task queue        if(i = = = ' Done ') {           This. Next (); }        return  This;    };  }) (i); }

Finally, we do the verification.

var New Task ();  Console.log (' 1 ')  t.job (' fuck '). Loop (3). Job ("World"). End (). loop (3). Job (' World '). End (). Job ('! ') ). Done ();  Console.log (' 2 ')  t.job (' fuck '). Loop (3). Job (' World '). Job ('! ') ). End (). done ();  Console.log (' 3 ')  t.job (' fuck '). Loop (3). Job (' World '). Job ('! '). End (). Job ('! ');

Well, chained calls play here. These demos, especially the lazy call after a little transformation, the function can be greatly enhanced, but here is no longer discussed.

Play a JS chained call

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.