JavaScript component journey (2) coding implementation and algorithmic _ javascript skills

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: SmartQueue. 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:

  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 could be retrieved as an 'Object', so use instanceof instead.    this.dependencies = dependencies instanceof Array ? dependencies : [];  };

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 could 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 ". In JavaScriptapply/callMethod to pass the new instance to the reconstructed_setupTaskMethod, as the context (context, also known as scope) of the method ),apply/callIt is a magic weapon for JavaScript to transfer context between methods. At the same time, users can definetask.fnThe context at execution time, and pass the custom data to the fn in execution.

What are the three-stage typical JavaScript objects?

  1. Define object constructor
  2. Define attributes and methods on the prototype
  3. New object.

ThereforeSmartQueue.TaskDefine properties and methods for the object's prototype. The previous analysis of tasks has several attributes and methods._setupTaskThe following are the properties 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 += ' depends 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, what we want to talk about here is simple and usually the least importanttoStringMethod. In some advanced languagestoStringMethods are recommended as best practices. Why? BecausetoStringYou can easily provide useful information in the debugger to easily write basic object information into logs. In a unified programming mode, you can implementtoStringYou can 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 provided by JavaScript 1.6 for Array objects is used here.forEachThis is because we assume that "external code" has been run before. SetlengthThe property is0As a result, it is cleared and all items (array units) are released ).

Last MethodfireIs the main method of the entire component. It is responsible for sorting all task queues and executing 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_sortingvar _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

  1. Access each node to be sorted
    1. If you have already accessed the service, the system returns
    2. Otherwise, mark it as accessed
    3. Find every node that it connects (where it depends)
    4. Jump to the inner layer 1 to access these nodes recursively.
    5. After the access, add the current node to the sorted list.
  2. 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(queue.length > _sorted[index].length), It indicates that the more nodes to be sorted have 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.

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.