所以,下面要为 SmartQueue.Task 对象的原型定义属性和方法。上期分析过 Task (任务)有几个属性和方法,部分属性我们已经在 _setupTask 中定义了,下面是原型提供的属性和方法:
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; } };如你所见,逻辑非常简单,也许你已经在一分钟内扫过了代码,嘴角不经意间露出一丝心领神会。不过,这里要说的是简单而且通常最不被重视的 toString 方法。在一些高级语言中,为自定义对象实现 toString 方法被作为最佳实践准则而推荐,为什么呢?因为 toString 可以很方便地在调试器中提供有用的信息,可以方便地将对象基本信息写入日志;在统一的编程模式中,实现 toString 可以让你少写一些代码。
嗯,我们继续推进,我们要实现 SmartQueue 的具体功能。上期分析过,SmartQueue 只有一个实例,因此我们决定直接在 SmartQueue 下面创建方法:
SmartQueue.init = function() { Q.forEach(function(queue) { queue.length = 0; }); };这里用到 JavaScript 1.6 为 Array 对象提供的遍历方法 forEach. 之所以这样写是因为我们假定“外部代码”已经在前面运行过了。设置 Array 对象的 length 属性为 0 导致,它被清空并且释放所有的项(数组单元)。
最后一个方法 fire, 是整个组件最主要的方法,它负责对所有任务队列进行排序,并逐个执行。由于代码稍长了一点,这里只介绍排序使用的算法和实现方式,完整代码在这里。
var _dirty = true, // A flag indicates weather the Queue need to be fired. _sorted = [], index; // Sort all Queues. // ref: 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); } } };我们将按任务指定的依赖关系对同一优先级内的任务进行排序,确保被依赖的任务在设置依赖的任务之前运行。这是一个典型的深度优先的拓扑排序问题,维基百科提供了一个深度优先排序算法,大致描述如下: