KISSY事件模型研究(3)

通过KISSY事件模型研究(1)KISSY事件模型研究(2)两篇文章,相信大家对KISSY的事件模型已经有了大致的了解,下面我们来介绍这个模型中的最后一个部分:对于特殊类型的事件处理。

相信用JavaScript做过二级菜单的同学一定都有这样经历,在处理mouseover,mouseout这些事件时是十分纠结的,在《ppk谈JavaScript》中,ppk就给出了自己对于这两个事件的解决方案,当然我们知道如果只有IE浏览器,这时我想将会十分幸福,因为IE特有的mouseenter,mouseleave这两个事件此时对于我们是再好不过的选择,可是世界就是残酷的,有时爱捣乱的不是IE反而是其他W3C浏览器,可是这就是前端必须面对的兼容性问题啊。

在KISSY的事件模型中考虑到了对这类特殊事件的支持,在对事件对象的兼容性保证中,我们从之前一篇文章已经了解到,KISSY会利用浏览器提供的原生事件对象,按照W3C标准进行标准化,生成一个新的事件对象模型,这在一定程度上解决了事件模型的兼容性问题,但是对于之前根本不存在的事件类型,这样的处理就根本一无是处,就如刚刚提到的mouseenter,mouseleave这两个事件,在W3C标准浏览器中,他们就根本不存在,何谈事件对象。但是我们也知道,利用已提供的mouseout,mouseover这两个事件是可以模拟mouseenter,mouseleave的,KISSY在这里提供了一种叫做specail事件的机制来处理这些特殊的事件类型,即可由KISSY来模拟产生这些特殊的事件类型,而使用者自己模拟的困扰。

在KISSY的事件体系中,专门准备了一个special对象来存储这些特殊类型的事件,而添加这些特殊类型的事件就如添加一个“插件”一样简单,我们完全可以按照自己的方法去模拟一个不存在的事件,然后把它添加到我们的KISSY事件体系中,这样便可以方便地进行注册。

首先我们看一下这些特殊类型的事件是如何加入并组织到KISSY的事件体系中:

KISSY.Event

对于这些特殊类型的事件,我们可以按照以下方法定义并添加:

首先获得KISSY的事件容器;然后往事件容器中的special对象中添加我们准备定义的特殊类型事件的的名称;之后再讲用于模拟该特殊类型事件的原始类型事件写入fix属性中;同时我们要设置setup方法,该方法会传入我们已经标准化过的事件对象,这里我们可以根据这个特殊类型事件对这个事件对象做进一步处理,例如修改事件对象的type属性;最后,也是最重要的,我们要设置handle方法,这个方法是用于用已有类型的事件进行模拟的,主要是判断事件是否发生,发生时则调用已注册的侦听器,这个方法会覆盖默认的_handle方法,如这里纯粹是为一个类型事件重命名,我们不设置handle方法,则系统会调用默认的_handle方法进行处理。

至此,我们已经对KISSY事件模型做了一个整体的概述,其中大部分涉及到的只是一些设计思想和数据结构,具体的代码并没有具体提及,之后的KISSY分析系列则会加入更多代码方面的讨论,欢迎大家继续阅读!

KISSY事件模型研究(2)

距离上一篇文章刚好一周的时间,一周上班的忙碌,真的回来之后都不想动手写点儿东东了~还是应该向承玉学习啊,博客能够保持那么高更新频率~话说这周博客访问突增,多亏玉伯的文章推荐哈~所以以后得再接再厉了!闲话不多说了,继续KISSY时间模型的研究~

话说上一篇文章后面得到了很多网友的回复和支持,其中一位网友提到了一个很直接的问题,不过我们暂时先不说这个问题,继续我们上次的话题。上次我们说到用KISSY的方法注册函数的时候,同一类型的DOM事件都是交由一个事件代理进行统一处理的,那么这次讨论点就从这个事件代理出发。

说到事件代理,相信对JavaScript有一定了解的人肯定都很熟习,说简单了,这个事件代理就是一个事件处理函数,我们在注册事件的时候,也是把它注册到事件目标对象上的。这里就涉及到事件的冒泡和捕获,感兴趣的同学可以先google了解一下这些基本概念。那为什么要用事件代理呢?其实这个话题完全可以写一篇博客自习研究,我这里就大概说一下我自己的想法。让我们先假设有一个div,其中包括了很多a,我们要做的就是对每一个a的click事件做不同处理(注意,这里有两个关键点,一个是“每一个a”,另一个是“不同处理”)。遇到这样一个问题,我想最开始的想法一定是对每个a注册一个函数,但是仔细想想,那么多a,以及那么多函数,这是多么棘手的一个问题啊?如果是统一的处理还很好办,大不了一个循环把所有a注册上一个处理函数,这样问题不就简单了,只是需要在这唯一一个处理函数里面进行判断做不同的处理罢了,这的确简化了我们最开始的想法,但是现在还存在一个可恶的循环啊,循环注册同一个事件处理函数,感觉真的好浪费啊!那么我们很自然的相当能不能就把这唯一一个事件处理函数注册到少量的元素上来达到目的呢?这大概就是事件代理的雏形吧,根据事件冒泡的冒泡性质(注意,并不是所有事件都会冒泡),我们可以把这唯一一个事件处理函数注册到div这个元素上,这时候我们只用往一个元素上注册唯一个事件处理函数,这不大大简化了我们最开始的想法?

有了事件代理的概念,我们再回过头想想我们的KISSY在注册事件的时候,对于同一类型的事件处理函数可以有很多,是不是很类似我们刚刚说的div中的a呢?但是我们的KISSY在注册这些事件的时候,不是每一个事件处理函数都是直接注册到事件目标对象上的,就像我们不准备用循环注册的方式在注册没一个a的处理函数,我们只是把他们暂存到侦听器里面(实际上,侦听器就是一个简单的数组),排成队列,然后依次调用即可,那我们div上的事件代理就像是我们KISSY中,同一类型的那个唯一的事件处理函数,我们只将它直接注册到事件目标对象上。请看下图:

event_target_object

我们往事件目标对象上注册了三个事件处理函数,但是这三个处理函数只是存储在侦听器这个中,而侦听器连接到事件代理上,真正注册到事件目标对象上的处理函数只有这个事件代理函数。因此对于DOM事件,我们需要用DOM方法,往事件目标对象上注册事件,处理函数就是这个事件代理,当事件发生时,就会调用这个事件代理进行处理,在事件代理中,我们只需依次从侦听器中调用用户注册的时间处理函数,就可以完成所有用户注册的所有事件处理函数的调用。需要特别说明的是,我们这里的事件代理,使用事件类型进行区分的,也就是说每个类型的事件都有一个事件代理以及一个侦听器数组,这样的划分粒度,即保证了函数处理、存储的简洁性,也保证了事件较高的可维护性,例如:我们可以简单滴取消所有某一类型的事件处理函数,这个工作对于直接使用DOM方法注册的事件处理函数,很难简单做到,相信诸位都有深刻体会。

说了这么说事件目标对象,那么我们应该自习分析一下这个概念。从字面上上理解,可以分为事件目标,和事件对象这两个方面,同时这正是KISSY源代码中对于事件相关文件的组织方式。

对于事件目标,其主要特点就是能够注册事件、撤销注册以及触发事件,对于DOM元素,除了text/commen两种类型的节点,其他都满足这个特点所有的DOM Node都满足这个特点,包括text/comment节点,只是这两种节点在实际应用中都不大实用(感谢玉伯指正),例如可以用addEventListener等方法注册一个事件处理函数,可以用removeEventListener这个方法撤销一个事件处理函数,所有的DOM事件都可分为UI Event、MouseEvent、KeyEvent,他们在浏览器内部都有相应的触发机制,构成了事件触发,因此DOM元素天生就是做事件目标的料根据DOM规范描述,EventTarget就是一个Interface,包含 addEventListener,removeEventListner,dispatchEvent 三个方法,因此,实现了这3个方法的对象,就是 EventTarget,DOM Node就是这样的(再次感谢玉伯指正);而我们要实现自定义函数,那么自定义事件目标就必须满足以上刚刚说的三个方面,因此在KISSY的事件模型中,自定义事件和DOM事件做了统一,只要自定义事件完成了三个相关函数的实现,就可以称之为事件目标。

事件对象,主要就是事件发生时,携带事件相关信息的对象,我们知道在DOM事件中,自身就有这个对象,只是这个对象存在诸多的兼容性问题,最基本的一个就是在IE8以下(包括),DOM事件发生时,这个事件对象是存储在window.event中,而W3C标准的浏览器,则是作为默认参数传递给事件处理函数,因此这类的不一致情况,导致了诸多了事件对象的兼容性问题,那么KISSY中为了处理这类的不兼容性,就用专门的fix对事件对象进行了修复,并提供了几个方便的事件对象函数的封装,例如阻止默认行为(preventDefault)、停止传播(stopPropagation)、终止事件(halt,包括停止事件传播和阻止默认行为)、立即停止传播(stopImmediatePropagation,与stopPropagation的主要区别是,该方法不仅阻止事件传播,还阻止之后侦听器中后续函数的发生)。我们的事件代理在事件触发时调用,其做的第一件事儿就是取出事件对象,然后传递给EventObject构造函数,构造出经过修复后的事件对象,这里的修复主要是参照W3C标准进行。文章最开始说到有一个网友提到了一个问题,其实这个问题就是和这个有关,他认为每次事件发生时,调用事件代理都需要构造一个EventObject,必然会影响性能,因此我也对这个性能有所担心,但是考虑到通常的事件并不会很高平率的触发,因此感觉这个性能问题影响不大,因此做了以下测试页:

请猛击这里查看DEMO

这个DEMO里,我用createEvent方法,创建了一个click事件,然后把这个事件分配给一个checkbox(以让大家能看见这个click的确发生了),一方面我们用传统的方法注册事件,不处理事件对象;另一方面,我们用KISSY的方法注册事件,这里的事件处理函数主要是处理一个进度条,当进度条完成后,记录消耗时间。因此,我们可以模拟大量点击事件,来考虑事件处理的性能问题,同时对于大量点击的模拟,我们加入一个延迟,可以模拟事件频发程度。对于这个DEMO,我们只能用非IE的Firefox和Chrome等浏览器测试(Opera也不支持),对于具体结果,大家可以先自己测试,观察一下。

点击Start按钮,可以看见右侧原本为选中的复选框已经选中,表示我们模拟的click事件已经有效了,我们如果用delay值为0的设置开始测试,会发现传统方式完成1000次点击、处理的时间在300ms左右,而KISSY方式消耗时间则在500ms以内(Chrome 6.0.472.25 dev),还是有尽40%的性能差异;当delay值设置增大时,两者的差距越来越小,因此可以看见两种方式的确有性能差异,但是当delay时间值增大,即浏览器有时间进行更复杂运算时,两者的差异已经很小,因此EventObject的创建有性能消耗,但是如果函数的调用没有如此频繁时及有足够的delay时间时,两者基本没有性能差异。因此可以得出结论,如果我们在实际使用中,KISSY的事件处理方式并不会造成较大的性能影响,我们用的主要事件很少会频发次数很高,今后可以再详细测试一下类似MouseMove的事件,同时可能还要考虑到具体的JavaScropt的具体运行环境。

今天主要的谈论集中在KISSY事件模型中的事件处理方式以及事件目标对象的概念,下次我们继续说说KISSY对一些特殊事件的处理方式,比如我们熟知的只有IE系列才拥有的MouseEnter/MouseLeave事件等,到此,我们对整个KISSY的事件模型和体系应该有大致了解,如果感兴趣的话,欢迎fork我们的源代码进行属于自己的开发,说不定你优秀的代码也能merge到KISSY的主分支中!

- EOF -

update. 2010.08.10 感谢网友hi,给出了支持IE测试的地址,欢迎强势围观。

我在你的demo上修改了一点点,可以支持ie了 代码见 http://www.nci365.com/test/e1.html
http://www.nci365.com/test/e2.html

e1.html和e2.html只有一句不同,见第119和120行
这两个比较下很有意思,让事件触发不是同一时刻并发,而是每个事件错开1ms触发,得到的测试结果就迥然不同。

KISSY事件模型研究(1)

最近做到一个需求,是基于KISSY完成核心代码的构建,对其中的事件模型很是有好感,借今日有空闲时间,正好研究研究,做了一下记录,供大家学习参考,有误之处还望大家及时指出!

首先让我们想一想,如果要给一个div元素添加一个click事件处理函数,常用的方法有两种吧:
一种:

div.addEventListener('click', foo, false);

或者:

div.onclick = foo;

这两种传统的事件注册方法,都涉及到三个要素,第一,要注册事件的元素target - div;第二,要注册的事件类型type - click;第三,要注册的事件处理函数fn - foo。这三个便是一个事件注册中需要关注的三个主要方面,我们称之为事件三要素。

那么我们就从刚刚所说的事件三要素中,考虑kissy是如何给事件目标对象注册事件的。 不过这里我还要啰嗦一句,我们这里所说的事件目标对象是在kissy中的一个特殊对象,除text/comment节点元素之外的所有DOM元素都是事件目标对象,当然,我们也可以有自己的事件目标对象,但它必须特殊处理才能成为事件目标对象,这个我们可以稍候再说。

1. 事件目标对象:这个即事件三要素中的target,它是整个事件注册过程中的主体,没有了它,一切无从谈起,因此我们首先要判断它是否是一个合法的事件目标对象,这里的要求很简单,只要是非text/comment节点元素的一切对象,都可以充当事件目标对象(不过就像我之前试过的,我们的自定义对象需要做一些特殊处理)。有了这个事件目标对象,我们要怎么处理它呢?自然是注册指定类型的事件处理函数,但是这里变遇到了个问题,我依然要像从前一样把事件处理函数直接注册到这个事件目标对象吗?大家都知道直接注册上去的弊端,其一就是无法统一管理,其二就是容易导致内存泄漏。对于这样两个严重的弊端,我们当然不能视为不久,因此这里我要隆重像大家介绍一个概念——事件缓存。

这里的事件缓存其实应该称为事件仓库更为且当,因为其中存放了所有使用我们特定事件注册方法注册的事件处理函数。正如刚刚所说的一样,那为什么会有事件仓库这个概念呢,首先是考虑到对事件的统一管理上,所有我们添加过的事件都能在仓库里面找到,而不像从前一样,事件都是独立分开,无法统一管理的,这里的管理,主要指的是对事件的添加、删除和处理;其二,事件仓库能够帮助我们减少内存泄漏的危险,从此,我们的事件不是直接注册到一个元素上,而是通过一个钩子,连接元素和事件仓库,所有的事件都存放在这儿,自然不会产生内存泄漏。对于刚刚说到的这个钩子,就是我们所说的事件目标对象的事件id,这个唯一的id标识于事件目标对象上,则可在事件目标对象和事件仓库中建立唯一连接。

这里设计的事件仓库数据模型如下图所示:

event_cache

这里对于事件仓库的组织结构很清晰,每个唯一事件id对应于唯一的事件目标对象,target即事件目标对象,events则存放该事件目标对象所有类型的事件,对于其中一个类型的事件,包括一个处理函数即事件代理和数个侦听器,每个侦听器中存放用户注册的事件处理函数和作用域。

既然已经了解了事件缓存的概念,那么需要在事件缓存和事件目标对象建立,就是刚刚提到的那个钩子,我们的事件注册函数自然会帮我们初始化并维护一个这些唯一的id用于建立事件缓存和事件目标对象的连接。则从此,我们已经把事件目标对象存放到事件缓存中,到此,事件目标对象的处理也算基本完成了。

2. 事件类型:下一个我们要考虑的要素就是我们要注册的事件类型。对于事件目标对象,在其上可发生诸多事件,我们就需要在上面注册指定类型的事件,然后当该事件发生时用相应的处理函数去处理,这个概念相信在DOM节点上大家都很清楚,正如文章开头给出的例子,我们就是在div这个事件目标对象注册了click这个事件类型的事件处理函数foo。那么这个事件类型怎么和事件仓库连接起来呢?再回过头去看那个数据模型图,那个events对象正是用来存储我们注册到事件目标对象的所有事件的对象。如果我们添加到其中的某一类型事件还不存在,就需要对该类型的事件进行初始化,包括建立一个统一事件代理,初始化侦听器数组。到此,我们的事件注册函数,对于事件类型的处理部分就完成了。

3. 事件处理函数:这一要素又是整个事件注册的核心要素。这里我就需要考虑到我们的自定义事件目标对象和传统的事件目标对象。传统的事件目标对象一般指DOM节点元素,这些对象有原生的事件注册方法(这里要指出的是kissy里面还有一个Node类型的DOM节点元素包装,其本质还是DOM节点元素),我们需要为他们注册侦听器,用我们统一的事件代理进行处理;而我们的自定义事件目标对象则不需要单独的事件注册,因为这些自定义事件都是需要“手动”触发的,因此不需单独的监听器绑定。最后已经往事件目标对象注册了侦听器,交由统一的事件代理,那么我们还需把我们自己需要绑定的事件处理函数,压入到我们的事件侦听器数组listeners里面去。这里说一下基本的事件处理思路,对于传统的事件目标对象,我们都往同一种类型是事件上注册唯一一个统一的事件代理,然后事件触发后有其统一处理,而我们自己的事件处理函数呢,存放于events对象的侦听器数组listeners里,在事件触发时,我们可以在事件代理再触发每个侦听器函数,这样就完整地模拟了传统事件目标对象的事件触发-处理;而对于我们的自定义事件目标对象,在手动调用fire方法触发事件时,就可以触发每个事件侦听器函数。

到此,一个完整的事件注册过程变完成了。其他事件模型的相关函数之后再介绍哈~

- EOF -

摆脱思维定式

已经入职实习一周的时间了,昨天有幸参加到了一个UED前端组的周会中,从中学到了不少业务方面的知识,虽然很多东西听得都是一知半解的,但是确实挺有收获的,尤其是听到沉鱼姐姐在讲到她们项目中的一个技术关键点时,大致问题如下:

请先看如下图:

这大致表示一个可视化编辑区域,用户可以选择往页面中嵌入任意多的容器,同时每个容器中又可以嵌入任意多个容器,如上图所示,我在一个容器box1中又嵌入了容器box2,又在容器box2中嵌入了容器box3,这种情况是为了需要满足复杂业务逻辑对页面的需求所实现,又因为这是一个WYSWYG编辑器,那么必然是加入对各个容器的可编辑按钮,接着就请看下图:

此图表示当鼠标hover到一个box上,出现的编辑区域提示,即该橙色框内的区域为box1的内容区域,可进行编辑。接下来,我们想像一下我们如何实现如此的页面结构,前一种状态的实现无非就是几个div嵌套(我们用类似box1-div的方式命名这几个div),再加上相关的css修饰即可,当前这种编辑状态,无非就是在当前容器上再加一个div(我们称之为box1-hover-div吧),以实现编辑区域指示。我想这是通常情况下大家很容易想到的解决方案。

那么此时就会遇到一个问题,我如果想编辑box2应该怎么办?很显然我要把鼠标移入到box2所在区域,但是我现在的编辑指示区域在box1上,如刚刚所述的解决方案,我已经在上面放了一个新的div作为编辑区域指示,那么很显然,我的鼠标移动是不可能再进入box2,因为在其上已经有box1-hover-div覆盖,我下面的box2-div是无论如何也不知道其上有鼠标的。可能这时候有些人会说我们可以通过复杂的事件关系,比如通过mousemove等事件,进行鼠标坐标测量,然后再判断相对位置来判定此时鼠标的位置,以及相应的行为,可是mousemove事件从名字就可详见其复杂性,以及后续判断逻辑发复杂性;此时可能我们想到再退而求其次,不用hover这种高级玩意儿了,用click来让用户选择编辑区域,那么mousemove这样的事件就可以消失了,但是坐标判断这种让人抓狂的玩意儿还是不能少吧?!

就在会上,很多FED提到了一些方案,比如让其transparent,但是这个方案从技术方面就是不对的;等等还有不少想法吧,我在其中也在想,认为这不大可能吧~

可是现在回想当时的情形,我们的思路已经完全集中在box1-hover-div,这个可恶的div上,我们在想如何让它在何时的情形下消失,然后鼠标自然就落到里面的box2上了,所以往往方案都是集中在处理这一个div上的~

------------------------华丽的分割线,也让我们自己再想一会儿这个------------------------------------









-------------------------------------华丽的分割线-------------------------------------------------------

既然这篇文章的标题是思维定式,那么何不尝试跳出这个定式,跳出这一个box1-hover-div,突然这时,冒出了四个box1-hover-div会怎么样啊?!我想说到这儿,我们智慧的FED一定知道解决方案了吧~想当年,我们为了圆角可以搞出四个无意义的div嵌套,为什么此时我们不继续将它改进后而“发扬光大”呢?这时我们用数个px的div分别作为box1-hover层的四边,这样中间自然就不会再有干扰的覆盖层遮盖box2及后续的box了。

当时沉鱼姐姐说出这个解决方案时,我们无不感叹其精妙之处,确实,这个方案没有华丽的JS判断优化,也没有高超的技术使用,用的不过就是另一种思维方式,我们通常的hover层就一个div,然后加上边框成为名副其实的一个“层”,但如今我们只需要其边框,这个“层”自然可以不完整,它为空心的不久正好满足要求么?

后来沉鱼姐姐补充到,她把这个方案给玉伯review的时候,玉伯也表示其实现方式和代码都比自己的方案要短小~

最后通过这个案例,我想表达的是,这样的思维定式确实是会禁锢我们前进的重要因素之一,如何摆脱这样情况也是我们需要考虑的重要问题!
p.s.现在在这个团队中实习,真的感到自己颇有收获,虽然时间不久,但是我想今后随着实习的深入,对自己的提升会是全方位的,不论是技术还是思想,我真的很庆幸能够获得这次实习机会,感谢小马和老三的帮助,还有一直默默支持我的

也谈排序算法

最近期末复习正好看到算法的分治策略,里面讲到两种排序算法:合并排序和快速排序。这里大概就谈下自己对算法的理解,代码不是最重要的,思想才是精华,所以这次不打算讲太多代码。

首先说一下分治法的基本思想,分治法是把一个规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同(这里需要注意分治算法与动态规划的区别)。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这里要谈到的合并排序和快速排序便是分治算法的典型应用之一。

首先看合并排序,其基本思想是将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终讲排好序的子集合合并成为所要求的排好序的集合。算法一般通过计算两个边界的中点作为分界点,然后再递归调用对左半边和右半边分别合并排序,递归中止条件自然是左边界小于右边界,即保证至少有两个元素参与排序。当一轮调用中,左半边和右半边分别排序完成后(或无法再划分进行排序后),就进行合并,其中对元素的真正排序便是在合并中完成的,利用一个临时数组完成对排序后数组两边元素的排序、合并。如下是一个合并排序的过程图,其中S代表mergeSort函数,即递归函数,其中参数待排序数组,左分界点,右分界点;m代表merge函数,即排序、合并函数,其参数分别为待合并数组,目的合并数组,合并左分界点,合并中点,合并右分界点。

mergesort

再来看看快速排序,其基本思想是对于输入的子数组a[p:r],按以下3个步骤进行排序:

  1. 分解(divide):以a[p]为某基准元素,将a[p:r]划分成3段,a[p:q-1], a[q]和a[q+1:r],使得a[p:q-1]中的任何元素都小于等于a[q],而a[q+1:r]中的任何元素大于等于a[q],q在划分中确定;
  2. 递归求解(conquer):通过递归调用快速排序算法,分别对a[p:q-1]和a[q+1:r]进行排序;
  3. 合并(merge):由于对a[p:q-1]和a[q+1:]r的排序在分解过程中就已经完成,所以当前a[p:q-1]和a[q+1:r]中的元素都已经排好序了,无须再执行任何计算,a[p:r]就已经完成排序了。

如下以a = [5, 3, 7, 2, 6, 4, 8, 1, 9]为例,具体分析一下快速排序的过程,【】表示划分元素,{}表示需交换元素。

quicksort

由上述分别对两种算法的说明,大致也能看出两算法的异同之处。两种算法都考虑到使用划分元素的方法进行分治的依据,但合并排序的划分元素是直接去两边界的中点,而快速排序的划分点则是在排序中找到;合并排序首先进行划分,然后再对划分的部分递归调用,而快速排序则是在排序过程中找到划分点,之后再递归调用;再次,合并排序需要借助一个临时数组完成合并操作,而快速排序则不需要特别地合并操作,也无需临时数组,其排序是在划分中完成,而合并则只需简单连接即可。

虽然自己已经对上述算法有了比较具体的了解,但是要深入理解还是需要慢慢体会,领悟其中玄机,同时也需要在不参考参考程序的过程中,完成自己程序的编写,这样不但锻炼了编程能力,更加需要对算法本身有较透彻的理解,所以下一步自己需要用一门语言用自己的方法实现算法。

Page 4 Of 2712345678...最旧 »