手机版

JavaScript组件之旅(二)编码实现与算法

时间:2021-10-25 来源:互联网 编辑:宝哥软件园 浏览:

首先要考虑它的源文件布局,也就是如何把代码拆分成单独的文件。你为什么要这么做?还记得上一期最后我提到这个组件会使用“外部代码”吗?为了区分代码的用途,决定将代码至少分为两部分:外部代码文件和智能队列文件。区分目的只是其中之一。其次,分散到独立的文件有利于代码维护。想象一下,在未来的某一天,你决定在队列管理现有的基本功能上增加一些新的扩展功能,或者将其打包为组件来实现特定的任务,而你又想保持现有的功能(内部实现)和调用方法(外部接口)不变,那么把新的代码写到单独的文件中是最好的选择。好了,下一期我们重点讲文档布局这个话题,现在开始开门见山。当然,第一步是为组件创建自己的命名空间,组件的所有代码都将被限制在这个顶级命名空间中:var smart queue=window . smart queue | | { };SmartQueue.version=' 0.1初始化时,如果遇到命名空间冲突,请将其拉过来使用。通常,这种冲突是由于反复引用组件代码造成的,所以“拉过来”会用同一个实现重写对象一次;在最坏的情况下,如果碰巧页面上的另一个对象也被称为智能队列,对不起,我将覆盖您的实现——。如果没有进一步的命名冲突,基本上两个组件可以和平运行。同时,给它一个版本号。然后,根据三个优先级为SmartQueue创建三个队列:var q=smartqueue.queue=[[],[],[]];每个都是一个空数组,因为还没有添加任务。对了,给它创建一个“快捷方式”,然后访问数组,直接写Q[n]。接下来,我们的主角Task在——隆重登场。这里定义了如何创建一个新的Task:var t=smart queue . Task=function(fn,级别,名称,依赖项){ if(fn的类型!==FUNCTION) {抛出新错误('无效参数类型: fn ');} this.fn=fnthis.level=_validateLevel(级别)?level : LEVEL _ NORMAL//检测名称类型this . name=type of name===STRING name?name : ' t ' _ id//依赖项可以作为“对象”检索,因此请改用instanceof。依赖=数组的依赖实例?依赖项:[];};里面的具体细节我就不说了,有必要评论一下。一般来说,我们的代码也可以描述自己,下面的代码也可以。在这里告诉客户(用户):如果您想创建一个新的智能队列实例。任务,则必须至少向该构造函数传递一个参数(最后三个参数可以省略以进行默认处理),否则,抛出异常服务。然而,这还不够。有时候,客户想从现有的Task中克隆一个新的实例,或者从“跛子”(一个具有部分Task属性的对象)中修复一个“健康的身体”(一个真实的Task对象实例),通过上面的构造方法,他们会有些心烦。——客户必须写如下:var task1=新智能队列。task (obj.fn,1 '',' ',我很懒,只想通过fn和依赖,不想做任何额外的事情。

好了,我们来重构构造函数:var _ setuptask=function (fn,level,name,dependencies) {if(类型为fn!==FUNCTION) {抛出新错误('无效参数类型: fn ');} this.fn=fnthis.level=_validateLevel(级别)?level : LEVEL _ NORMAL//检测名称类型this . name=type of name===STRING name?name : ' t ' _ id//依赖项可以作为“对象”检索,因此请改用instanceof。依赖=数组的依赖实例?依赖项:[];};var T=智能队列。task=function(task){ if(arguments . length 1){ _ setuptask . apply(this,arguments);} else { _setupTask.call(this,task.fn,task.level,task.name,task . dependencies);} //初始化任务的上下文/范围和数据。this . context=task . context | | window;this . data=task . data | | { };};这样,原来的构造就可以继续工作,上面的懒人可以这样引入一个“废人”:vartask 1=new smart queue . task({ fn 3360 obj . fn,dependencies 3360 obj . dependencies });当构造函数接收到多个参数时,将根据之前的方案对其进行同等处理。否则,唯一的参数被视为任务对象或“跛子”。在这里,新实例通过JavaScript中的apply/call方法传递给重构的_setupTask方法,该方法充当该方法的上下文(也称为作用域)。Apply/call是JavaScript在方法之间传递上下文的法宝,要仔细理解。同时,允许用户在执行过程中定义task.fn的上下文,并将定制的数据传递给正在执行的fn。什么是经典的JavaScript对象三阶段?定义对象的构造函数在原型上定义新对象的属性和方法以供使用。因此,为智能队列的原型定义了以下属性和方法。任务对象。上一期分析的Task有几个属性和方法,其中一些已经在_setupTask中定义,下面是原型提供的属性和方法:T. prototype={enabled : true,register 3360 function(){ var queue=q[this。级别];if(_findTask(queue,this.name)!==-1) {引发新错误('指定的名称存在: ' this . name);} queue . push(this);},changeTo:函数(级别){ if(!_validateLevel(级别)){抛出新错误('无效参数:级别');} level=parseInt(level,10);if(this . level===level){ return;} Q[this.level]。移除(此);this.level=levelthis . register();},execute : function(){ if(this . enabled){//传递上下文和数据this.fn.call(this.context,this . data);} },tostring : function(){ var str=this . name;if(this . dependencies . length){ str='依赖于: [' this.dependencies.join(',')']';}返回字符串;} };如你所见,逻辑很简单。也许你在一分钟内扫完了代码,嘴里流露出一丝理解。然而,我想在这里谈论的是简单且通常最不被重视的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会导致它被清空,并且释放所有项(数组单元格)。

最后一个方法火,是整个组件最主要的方法,它负责对所有任务队列进行排序,并逐个执行。由于代码稍长了一点,这里只介绍排序使用的算法和实现方式,完整代码在这里var _dirty=true,//一个标志指示队列是否需要触发_sorted=[],index//对所有队列进行排序//参考: http://en.wikipedia.org/wiki/Topological_sortingvar _访问=函数(队列,任务){ if(任务_visited=1) {任务._已访问;返回;}任务_ visited=1;//找出并访问所有依赖项var .依赖关系=[],我;task.dependencies.forEach(函数(依赖关系){ i=_findTask(队列,依赖关系);如果(我!=-1){依赖项。推送(队列[I]);} });依赖性。foreach(function(t)){ _ visit(queue,t);});如果(任务_visited===1) { _sorted[index].推送(任务);} },_ start=function(queue){ queue。foreach(函数(任务){ _ visit(队列,任务);});},_ sort=function(sup rest){ for(index=LEVEL _ LOW;索引=电平_高索引){ var queue=Q[index];_ sorted[index]=[];_开始(队列);if(!抑制queue.length _sorted[index].长度){引发新错误('在队列: '中找到循环');} } };我们将按任务指定的依赖关系对同一优先级内的任务进行排序,确保被依赖的任务在设置依赖的任务之前运行。这是一个典型的深度优先的拓扑排序问题,维基百科提供了一个深度优先排序算法,大致描述如下图片来自维基百科

如果维基百科中每个要排序的节点都被访问过,就返回,否则标记为已访问并找出它的连接(这里是从属的),跳转到内层1递归访问这些节点,然后将当前节点添加到排序列表中,继续访问下一个节点。如果A依赖B,B依赖C,C依赖A,那么这三个节点就形成了循环依赖。指出该算法不能检测循环依赖。通过标记节点是否被访问过,可以解决循环依赖导致的递归无限循环。我们来分析一下循环依赖的场景:从节点A开始时,标记为已访问,从节点C返回到节点A时,已经被访问。但是此时C并不知道A是否在其上游链,所以不能直接判断已经发生循环依赖,因为A可能是其他已经“处理”的节点(运行内部递归后)。如果知道节点是否第一次被访问,就可以判断是哪种情况。修改以上算法,将“是否访问过”改为“任务”。_已访问”。仅当节点被访问过一次时(任务。_visited===1)它将被添加到排序列表中。在所有遍历之后,如果要排序的节点数多于已经排序的节点数(队列。length _ sorted [index]。长度),这意味着排序列表中的额外节点具有循环依赖性。至此,队列管理组件的编码实现已经完成。什么事?怎么用?很简单:var t1=newsmartqueue . task(function(){ alert(' hello,world!'。);}),t2=新的智能队列。任务(函数(){ alert('高级任务有名称'));},2,“my name”);t1 . register();T2 . register();smartqueue . fire();更多的功能,比如任务依赖,等着你去探索。本期发布的所有代码都是部分片段,部分助手方法代码没有发布。请参见此处的完整代码。稍后,我们将介绍如何管理组件文件和构建组件,我们将在下一期中看到您。

版权声明:JavaScript组件之旅(二)编码实现与算法是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。