JavaScript components: Coding implementation and Algorithms

Source: Internet
Author: User

In the previous period, we discussed the design of the Queue management component and gave it a loud and unique name: Smart Queue. this time, we will put the previous design results into practice and use code to implement it.

First, we need to consider its source file layout, that is, how to split the code into independent files. Why? Remember that I mentioned at the end of the last issue that this component would use "external code? To differentiate the purpose of the code, we decided to divide the code into at least two parts: the external code file and the Smart Queue file.

Differentiation is only one of its purposes. Second, distribution to independent files facilitates code maintenance. Imagine that in the future, you will decide to add some new extended functions or package them into components for specific tasks based on the basic functions of existing queue management, to keep the existing functions (internal implementation) and calling methods (external interfaces) unchanged, writing new code to a separate file is the best choice.

Well, we will focus on the topic of file layout in the next period. Now we are about to start with the topic. The first step is to create your own namespace for the component. All the code of the component will be restricted to the top-level namespace:

Var SmartQueue = window. SmartQueue || {};

SmartQueue. version = '0. 1 ';

During initialization, if a namespace conflict occurs, pull it for use. This conflict is usually caused by repeated reference of component code. Therefore, "pull over" will rewrite the object with the same implementation. In the worst case, if another object on the page is also called SmartQueue, sorry, I will overwrite your implementation-if there is no further Name Conflict, basically the two components can run in peace. At the same time, give it a version number.

Next, create three queues for SmartQueue with three priorities:

Var Q = SmartQueue. Queue = [[], [], [];

Each is an empty array, because no task has been added. By the way, you can create a "shortcut" for it. You can access the array and write Q [n] directly.

Next, we will present our main Task-how to create a new Task, which is defined here:

The specific details are not mentioned, and comments are necessary. Generally, our code can also be self-described, and the code below is also like this. Tell the customer (User): You want to create a new SmartQueue. for a Task instance, you must pass at least one parameter to this Constructor (the last three can be omitted for default Processing). Otherwise, an exception is thrown.

However, this is not enough. Sometimes, the customer wants to clone a new instance from an existing Task or an object with partial Task attributes) after fixing the "Healthy body" (a real Task object instance), the above constructor is a bit uncomfortable-the customer has to write like this:

Var task1 = new SmartQueue. Task (obj. fn, 1, '', obj. dependencies );

I am very lazy. I just want to pass the fn and dependencies attributes and don't want to do anything else. Let's refactor the constructor:

Var _ setupTask = function (fn, level, name, dependencies ){

If (typeof fn! = FUNCTION ){

Throw new Error ('invalid argument type: fn .');

}

This. fn = fn;

This. level = _ validateLevel (level )? Level: LEVEL_NORMAL;

// Detect type of name

This. name = typeof name ===string & name? Name: 'T' + _ id ++;

// Dependencies cocould be retrieved as an 'object', so use instanceof instead.

This. dependencies = dependencies instanceof Array? Dependencies: [];

};

Var T = SmartQueue. Task = function (task ){

If (arguments. length> 1 ){

_ SetupTask. apply (this, arguments );

} Else {

_ SetupTask. call (this, task. fn, task. level, task. name, task. dependencies );

}

// Init context/scope and data for the task.

This. context = task. context | window;

This. data = task. data || {};

};

In this way, the original constructor can continue to work, and the lazy person above can input a "disability" as follows ":

Var task1 = new SmartQueue. Task ({fn: obj. fn, dependencies: obj. dependencies });

When the constructor receives multiple parameters, it is processed in the same way as the previous solution. Otherwise, the unique parameter is regarded as the Task object or the "disability body ". Here, the apply/call method in JavaScript is used to pass the new instance to the reconstructed _ setupTask method as the context of the method (context, also known as scope ), apply/call is a magic weapon for JavaScript to transfer context between methods. At the same time, you can define the context of task. fn during execution and pass custom data to fn during execution.

What are the three-stage typical JavaScript objects?

Define object constructor

Define attributes and methods on the prototype

New object.

Therefore, attributes and methods must be defined for the prototype of the SmartQueue. Task object. Several attributes and methods of Task have been analyzed in the previous period. Some attributes have been defined in _ setupTask. The following are the attributes and methods provided by the prototype:

T. prototype = {

Enabled: true,

Register: function (){

Var queue = Q [this. level];

If (_ findTask (queue, this. name )! =-1 ){

Throw new Error ('specified name exists: '+ this. name );

}

Queue. push (this );

},

ChangeTo: function (level ){

If (! _ ValidateLevel (level )){

Throw new Error ('invalid argument: level ');

}

Level = parseInt (level, 10 );

If (this. level = level ){

Return;

}

Q [this. level]. remove (this );

This. level = level;

This. register ();

},

Execute: function (){

If (this. enabled ){

// Pass context and data

This. fn. call (this. context, this. data );

}

},

ToString: function (){

Var str = this. name;

If (this. dependencies. length ){

Str + = 'pends on: ['+ this. dependencies. join (', ') +'] ';

}

Return str;

}

};

As you can see, the logic is very simple. Maybe you have scanned the code within one minute, and the corners of your mouth reveal a bit of luck. However, here we will talk about the toString method that is simple and usually least valued. In some advanced languages, toString methods for custom objects are recommended as the best practices. Why? ToString allows you to easily provide useful information in the debugger and easily write basic object information into logs. In a unified programming mode, toString allows you to write less code.

Well, let's continue. We need to implement the specific functions of SmartQueue. The previous analysis showed that there was only one SmartQueue instance, so we decided to create a method directly under SmartQueue:

SmartQueue. init = function (){

Q. forEach (function (queue ){

Queue. length = 0;

});

};

The Traversal method forEach provided by JavaScript 1.6 for Array objects is used here. The reason for writing this is that we assume that "external code" has been run before. The length attribute of the Array object is set to 0, which is cleared and all items (Array units) are released ).

The last method, fire, is the main method of the entire component. It sorts all task queues and executes them one by one. The Code is a little longer. Here we only introduce the algorithm and implementation method used for sorting. The complete code is here.

Var _ dirty = true, // A flag indicates weather the Queue need to be fired.

_ Sorted = [], index;

// Sort all Queues.

// Ref: http://en.wikipedia.org/wiki/Topological_sorting

Var _ visit = function (queue, task ){

If (task. _ visited> = 1 ){

Task. _ visited ++;

Return;

}

Task. _ visited = 1;

// Find out and visit all dependencies.

Var dependencies = [], I;

Task. dependencies. forEach (function (dependency ){

I = _ findTask (queue, dependency );

If (I! =-1 ){

Dependencies. push (queue [I]);

}

});

Dependencies. forEach (function (t ){

_ Visit (queue, t );

});

If (task. _ visited === 1 ){

_ Sorted [index]. push (task );

}

},

_ Start = function (queue ){

Queue. forEach (function (task ){

_ Visit (queue, task );

});

},

_ Sort = function (suppress ){

For (index = LEVEL_LOW; index <= LEVEL_HIGH; index ++ ){

Var queue = Q [index];

_ Sorted [index] = [];

_ Start (queue );

If (! Suppress & queue. length> _ sorted [index]. length ){

Throw new Error ('cycle found in queue: '+ queue );

}

}

};

Tasks with the same priority are sorted by the dependencies specified by the task to ensure that the dependent tasks run before the dependent tasks are set. This is a typical deep-priority topological sorting problem. Wikipedia provides a deep-priority sorting algorithm, which is roughly described as follows:

Picture from Wikipedia

Access each node to be sorted

If you have already accessed the service, the system returns

Otherwise, mark it as accessed

Find every node that it connects (where it depends)

Jump to the inner layer 1 to access these nodes recursively.

After the access, add the current node to the sorted list.

Continue to access the next

If A depends on B, B depends on C, and C depends on A, the three nodes form A circular dependency. This algorithm cannot detect circular dependencies. By marking whether a node has been accessed, the recursive endless loop caused by loop dependency can be solved. Let's analyze the cycle dependency scenarios:

When starting from node A, it is marked as accessed. When node C returns to node A, it is already accessed. However, at this time, C does not know whether A is in its own upstream chain, so it cannot be directly determined that A has A circular dependency, because A may be another one that has been "processed" (after running the internal recursion). If we know whether the node has been accessed for the first time, we can determine the situation.

Modify the above algorithm and change "accessed" to "Access count" (task. _ visited ++ ). Only when the node is accessed once (task. _ visited = 1) to add it to the sorted list. After all the traversal, if the number of nodes to be sorted is more than the number of sorted nodes (queue. length> _ sorted [index]. length) indicates that the node to be sorted has a circular dependency.

So far, the encoding Implementation of the queue management component has been completed. What? How to use it? It's easy:

Var t1 = new SmartQueue. Task (function (){

Alert ("Hello, world! ");

}), T2 = new SmartQueue. Task (function (){

Alert ("High level task has name ");

}, 2, 'myname ');

T1.register (); t2.register ();

SmartQueue. fire ();

More functions, such as task dependencies, are waiting for you to explore.

The Code pasted in this issue is partial fragments, and some helper method codes are not pasted out. To view the complete code, visit here. The following describes how to manage component files and build components.

Var T = SmartQueue. Task = function (fn, level, name, dependencies ){

If (typeof fn! = FUNCTION ){

Throw new Error ('invalid argument type: fn .');

}

This. fn = fn;

This. level = _ validateLevel (level )? Level: LEVEL_NORMAL;

// Detect type of name

This. name = typeof name ===string & name? Name: 'T' + _ id ++;

// Dependencies cocould be retrieved as an 'object', so use instanceof instead.

This. dependencies = dependencies instanceof Array? Dependencies: [];

};

Related Article

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.