JavaScript重构深入剖析

开发 前端
JavaScript是前台代码中重要组成部分,随着版本的延续,产品越做越大,JavaScript层面的重构,需要在整个过程中逐步强化起来。

通常我们的团队中,开发人员在Java语言层面具备相当的技术素养,经验丰富,而且有许多成熟的、合理的规约,类型繁多的代码隐患检查工具,甚至在团队间还有计划内的评审和飞检。但是前端的代码不似后台,就像一个没人疼的孩子,不仅仅容易被低估、被轻视,导致质量低劣、可维护性差,技能上,更缺少优秀的前端开发人员。

JavaScript是前台代码中重要组成部分,随着版本的延续,产品越做越大,JavaScript层面的重构,需要在整个过程中逐步强化起来。

当代码量达到一定程度,JavaScript最好能够与页面模块组件(例如自定义的FreeMarker标签)一起被模块化。

模块化带来的最大好处就是独立性和可维护性,不用在海量的js中定位问题位置,简单了,也就更容易被理解和接受,更容易被定制。

模块之间的依赖关系最好能够保持简单,例如有一个common.js,成为最通用的函数型代码,不包含或者包含统一管理的全局变量,要求其可以独立发布,其他组件js可以轻松地依赖于它。举个例子,我们经常需要对字符串实现一个trim方法,可是js本身是不具备的,那么就可以在这个common.js中扩展string的prototype来实现,这对外部的使用者是透明的。

模块划分和命名空间

使用命名空间是保持js互不干扰的一个好办法,js讲究起面向对象,就必须遵循封装、继承和多态的原则。

参照Java import的用法,我希望命名空间能带来这样的效果,看一个最简单的实例吧:

我有一个模块play,其中包含了一个方法webOnlinePlay,那么在没有import这个模块的时候,我希望是js的执行是错误的:

  1. webOnlinePlay(); //Error! 无法找到方法 

但是如果我引入了这个模块:

  1. import("play");  
  2. webOnlinePlay(); //正确,能够找到方法  

其实实现这样的效果也很简单,因为默认调用一个方法webOnlinePlay()的实质是:window.webOnlinePlay(),对吗?

所以在import("play")的时候,内部实现机制如下:

  1. var module = new playModule(); 

对于这个模块中的每一个方法,都导入到window对象上面,以直接使用:

  1. window[methodName] = module[methodName]; 

其实这里并没有什么玄机,但是这种即需即取的思想却给前端重构带来了一个思路,一个封装带来的可维护性增强的思路,不是吗?

聪明的你也许还会提到一个问题:

如果我没有import这个play模块,这个页面都不需要,那我能否连这个play.js都不加载呢?

当然可以,请关注下一页———关于js的动态加载的部分。

#p#

JavaScript的动态加载

前一节留下了一个问题,如果JS分门别类也清晰了,那我现在需要在必要的情况下才加载某一模块的JS,这个怎么实现呢?

 

方法一,最简单也是最容易被接受的方法,通过后台代码来控制,还是少些复杂的JS吧,通过一个标签、一个分支判断,就可以做到,何乐而不为呢?

 

方法二,如果要使用纯JS来控制,那么看看这样如何:

  1. $.ajax(){   
  2.     url:"xxx/play.js";   
  3.     ……   
  4.     success:function(res){   
  5.         eval(res.responseText);   
  6.     }   

原理是很简单,不过有一个藏匿着的魔鬼:eval,js加载的生效就靠它了,那么执行的上下文就在它的里面,这就会带来一些潜在的问题,而且,调试也变得困难。

方法三,通过添加<script>标签的方式来动态引入脚本:

原理相信大家也马上能领悟个大概了,需要的时候动态地往页面的<head>里面写一对<script>标签,让浏览器自己去取需要的js,这样的就解决了方法二里面的魔鬼eval的问题,是一个比较好的方法:

  1. <script src="xxx/play.js" ... /> 

这里啰嗦一句,<script>标签中的src——本质上不就是对src所表示的地址发送一个get请求吗?这虽然看起来有点歪门邪道,却恰恰是一个跨域问题的解决办法!

#p#

JavaScript的测试

进行JavaScript重构时,我希望引入易于使用的测试框架来保证重构的顺利进行,未来能持续通过测试代码对JavaScript逻辑的正确性做保障。

JsUnit (http://sourceforge.net/projects/jsunit/,http://www.jsunit.net/)

JsUnit是一个独立的JavaScript单元测试框架,和JUnit差不多,没有上手难度,包括传统的setUp和tearDown,提供的assert方法也和JUnit类似,多了assertNaN和assertUndefined等等JavaScript特有的方法。测试页面必须在<head>里面引入jsUnitCore.js这个js文件。

测试套件的支持:提供了addTestPage和addTestSuite;

测试日志的支持:包括warn、info和debug三种日志级别,前端编码不似后台代码,正式代码中不宜使用过多log,再说log也只有FF下才支持,现在好了,在测试代码里尽情打吧。

千言万语不及一个例子:

  1. <script language="javascript" src="jsUnitCore.js"></script>   
  2. <script language="javascript" src="play.js"></script> //模块JS  
  3.  
  4.     function testWithMainProcess() {   
  5.         assertEquals("Web play url""##http://...##", webOnlinePlay());   
  6.     } 

项目的代码里到处是Ajax调用,要做单元测试,看来打桩是不可避免了。Mock类的工具有许多,比如适合JQuery的QMock:

  1. var mockJquery = new Mock();   
  2.    mockJquery   
  3.       .expects(1)   
  4.       .method('ajax')   
  5.       .withArguments({   
  6.          url: 'http://xxx,   
  7.          success: Function,   
  8.          dataType: "jsonp"   
  9.       })   
  10.       .callFunctionWith({ feed : { entry : "data response" }}); 

这个桩正是mock了一个假的ajax jason返回:[feed:[entry:"data response"]],看看,使用就和以前接触过的EasyMock差不多嘛。

对于JavaScript测试框架感兴趣的同学还可以了解一些其他的测试框架,例如JSpec。

单元测试代码建议就放在模块的包内:test.html,即便理想状况下,模块单独发布时,也是伴随着测试用例的可靠的前端代码。

从哪些JavaScript代码开始做?

1、函数式的代码。这样的代码保证独立性好,也不需要打什么桩,测试成本低,如果不明白函数式的代码的含义,请参见“函数式编程”。

2、复杂的逻辑。

是否尝试TDD?不建议在我们团队内部使用,前端TDD需要更高的技巧,对人的因素要求更高。如果有一天,后台Java代码的TDD做好了,那么换成JavaScript的代码,没有本质区别。

如果效果得当,为什么不能把JavaScript的UT集成到ICP-CI上作为持续集成的一部分呢?

#p#

JavaScript编码规则

没有规矩,不成方圆,JavaScript带来了灵活性,也带来了不受控的变量和访问,所以要用规则限制它。一支成熟的团队,还是一支新鲜的团队,规则应当是不一样的,我只是列出一些常见的或者有效的办法,来约束跳跃的开发人员,思维可以任意飞跃,代码却要持续受控。当然,任何规则都是建立在一定的认知基础之上的,面向对象JavaScript的基础是必备的,否则一切无从谈起。

 

变量和方法控制:

模块开发不允许存放独立的全局变量、全局方法,只允许把变量和方法放置到相应模块的“命名空间”中,对此的解释请参见此文。实在心痒了,那么使用匿名函数如何?

  1. (function() {   
  2.     var value = 'xxx';   
  3.     var func = function() {...};   
  4. })(); 

 

模块化需要严格控制住代码的区域性,这不仅仅是代码可维护性、可定制性的一方面,同时也让JavaScript引擎在属性和方法使用完毕后及时地回收掉。

不允许在模块代码中污染原生对象,例如

  1. String.prototype.func = new function(){...}; 

如此的代码必须集中控制,例如统一放置在common.js中,严格保护起来。

数据存放约束:

普通变量、prototype变量和function变量分而治之,方法名一律大写开头,变量名还是遵从骆驼命名法如何:

  1. function T(name){   
  2.     T.prototype._instance_number++;   
  3.     this.name = name;   
  4.     this.showName=function(){   
  5.         alert(this.name);   
  6.     }   
  7. };   
  8. T.prototype = {   
  9.     _instance_number:0,    
  10.     getInstanceNum: function(){    
  11.         return T.prototype._instance_number;   
  12.     }   
  13. };  
  14.  
  15. var t = new T("PortalONE");   
  16. t.showName();   
  17. new T("Again");   
  18. alert(t.getInstanceNum()); //打印:2 

 

这里有意做了一件事情,T内部的属性和私有方法使用下划线开头,这样很好地实现了封装(上述代码中如果使用t.instanceNum,是无法访问到这个值的),如果这段代码都看不懂的话,赶紧温习一下JavaScript的面向对象吧 :)。JavaScript中提供了闭包和原型两种办法来实现继承和多态,关于重构中应用这一点,后续的章节我再啰嗦吧。

另外,优先使用JavaScript的原生对象和容器,比如Array,Ajax的数据类型统一切到JSON上来,尽量不要使用隐藏域;另外,通常是不允许随意扩展DOM对象的。

至于模块间的通信:模块间的通信意味着模块间的耦合性,是需要严格避免的;通信的途径通常使用方法级属性或者模块级的prototype变量。

 

DOM操纵规则:

在模块代码中,通常要求把对DOM的操纵独立到模块js中,应当避免在DOM模型上显示地写时间触发函数,例如:

  1. <div onclick="xxx" /> 

借助JQuery基于bind的一系列方法,把行为逻辑独立出来以后,完全可以看到清爽的HTML标签。

DOM对象的访问通常使用id来查找,偶有根据name来查找的,过多次数地、不合理地遍历DOM树是前端性能保持的大忌。

 

CSS的样式控制:

(1)尽量拒绝style="xxx"的写法,主要目的是将样式统一到主题样式表单中,当然主题样式表单也是按模块存放的,对于不同语种的定制和不同风格的切换带来便利。

(2)规约JavaScript对样式的操纵,理想状况下,封装性好的UI可以自由地替换它的样式集合。

 

以上只能算冰山一角,抛砖引玉,实际项目中需要在开发过程中逐步细化和完善。

#p#

利用原型和闭包,完成组件方法

终于要定义一个组件方法了,利用原型来实现。看看这样如何:

  1. function Player(name){   
  2.     Player.MIN_EXTENDED_TIME = 1;   
  3.     Player.MAX_EXTENDED_TIME = 3;   
  4.     this._name = name;   
  5. };   
  6. Player.prototype.setName = function(name){   
  7.     this._name = name;   
  8. };   
  9. Player.prototype.toString = function(){   
  10.     return "Player: " + this._name;   
  11. };  
  12.  
  13. var player = new Player("WindowsMediaPlayer");   
  14. alert(player.toString()); //输出WindowsMediaPlayer   
  15. player.setName("RealPlayer");   
  16. alert(player.toString()); //输出RealPlayer   
  17. alert(Player.MAX_EXTENDED_TIME); 

恩,有封装、有常量、也有复写了Object的toString方法,至于继承之类的事情,咱们后面再说,初看看还不错。可是这样的组件方法定义不够优雅,也不够直观,方法都是放在独立的位置定义的,并没有和最开始的组件方法放置在一起,如果能像Java那样定义岂不更好?

对了,可以用闭包来实现。试试看吧:

  1. function Player(name){   
  2.     Player.MIN_EXTENDED_TIME = 1;   
  3.     Player.MAX_EXTENDED_TIME = 3;   
  4.     this._name = name;   
  5.     this.setName = function(name){   
  6.         this._name = name;   
  7.     };   
  8.     this.toString = function(){   
  9.         return "Player: " + this._name;   
  10.     };   
  11. };  
  12.  
  13. var player = new Player("WindowsMediaPlayer");   
  14. alert(player.toString()); //输出WindowsMediaPlayer   
  15. player.setName("RealPlayer");   
  16. alert(player.toString()); //输出RealPlayer   
  17. alert(Player.MAX_EXTENDED_TIME); 

 

不像Groovy里面,闭包做了很大程度上的强化,包括新的语法的支持;JavaScript的闭包是很简单的闭包,它没有特殊的需要额外学习的语法,任意一个function,里面只要包含未绑定变量,这些变量是在function所属的上下文环境中定义的,那么,这个function就是闭包。顺便罗嗦一句,和闭包相反的,不正是不包含任何未绑定变量的函数式代码吗?

写是写好了,可是转念一想,Player应当只有一份,它是单例的,最好我也能像Java那样弄一个单例模式出来 :),可是事不遂愿,我没有办法在JavaScript做一个private的构造器,用这种思路去实现单例模式似乎不可行……

怎么办?

然而天无绝人之路,我控制不了你new一个Player的对象,我却可以控制你new出来的这个Player对象的属性和行为!当你需要使用你new出来的Player的对象的时候,你发现根本无法完成,或者它只是一个空壳!真正的东西还是要靠单例中经典的getInstance方法来获得:

  1. function Player(){   
  2.     throw new Error("Can not instantiate a Player object.");   
  3. }; //这只是个空壳  
  4.  
  5. (function(){ //这才是货真价实的东西   
  6.     Player.MIN_EXTENDED_TIME = 1;   
  7.     Player.MAX_EXTENDED_TIME = 3;   
  8.     Player._player = false;   
  9.     Player.getInstance = function(){   
  10.         if(!Player._player){   
  11.             alert("Init...");   
  12.             Player._player = {   
  13.                 _name : name,   
  14.                 setName : function(name){   
  15.                     this._name = name;   
  16.                 },   
  17.                 toString : function(name){   
  18.                     return "Player: " + this._name;   
  19.                 }   
  20.             };   
  21.         }   
  22.         return Player._player;   
  23.     };   
  24. })();  
  25.  
  26. //var player = new Player(); //new Player()会抛出异常   
  27. var player1 = Player.getInstance();   
  28. var player2 = Player.getInstance();   
  29. player2.setName("RealPlayer");   
  30. alert(player2.toString()); //输出RealPlayer 

 

好,真不错,单例模式在JavaScript下也成功实施了——你要胆敢new Player();就会抛出一个异常,这样什么也得不到,只有用getInstance方法得到的对象才是真真正正的Player对象。上面的代码整个执行的结果,只弹出了一次"Init..."的对话框,说明真正的“构造器逻辑”只调用了一次。

都做到这份上了,依然有小小的遗憾,Player的定义依然被拆分成了两部分,一部分定义空壳,一部分是一个匿名函数来定义Player的常量和getInstance方法。这两部分就不能合二为一么?

能。只需要用到一个小小的匿名函数,如果耐心从头看到这里,也一定能理解:

  1. var Player = (function(){   
  2.     Player = function(){ //这只是个空壳   
  3.         throw new Error("Can not instantiate a Player object.");   
  4.     };   
  5.     Player.MIN_EXTENDED_TIME = 1;   
  6.     Player.MAX_EXTENDED_TIME = 3;   
  7.     Player._player = false;   
  8.     Player.getInstance = function(){   
  9.         if(!Player._player){   
  10.             alert("Init...");   
  11.             Player._player = {   
  12.                 _name : name,   
  13.                 setName : function(name){   
  14.                     this._name = name;   
  15.                 },   
  16.                 toString : function(name){   
  17.                     return "Player: " + this._name;   
  18.                 }   
  19.             };   
  20.         }   
  21.         return Player._player;   
  22.     };   
  23.     return Player; //把修缮完工的Player这个组件方法返回   
  24. })();  
  25.  
  26. //var player = new Player(); //new Player()会抛出异常   
  27. var player1 = Player.getInstance();   
  28. var player2 = Player.getInstance();   
  29. player2.setName("RealPlayer");   
  30. alert(player2.toString()); //输出RealPlayer 

 

到此,终于如释重负,深入理解JavaScript面向对象,用好原型和闭包这两把锋利的武器,才能写出优秀的前端代码来。

#p#

利用继承来做事

终于要说到JavaScript的继承了,原型链继承是最常用的一种方式:

  1. function Video(){};   
  2. function Movie(){};   
  3. Movie.prototype = new Video();   
  4. Movie.prototype.constructor = Movie; //不要丢失构造器 

啰嗦一句,如果我拿到的是方法的实例,一样可以做继承:

  1. function Video(){};   
  2. function Movie(){};   
  3.  
  4. var video = new Video();   
  5. video.size = 3;   
  6. video.toString = function(){   
  7.     return "video";   
  8. };   
  9. video.getName = function(){   
  10.     return "VideoXXX";   
  11. };   
  12. var movie = new Movie();   
  13. (function inherit(parent,child){   
  14.     for(var ele in parent){   
  15.         if(!child[ele]) //在child不包含该属性或者方法的时候,才会拷贝parent的一份   
  16.             child[ele] = parent[ele];              
  17.     }   
  18. })(video,movie); //匿名函数调用的方式    
  19.     
  20. alert(movie.size); //3   
  21. alert(movie.toString()); //[object Object]   
  22. alert(movie.getName()); //VideoXXX 

可是这种方法是不纯粹继承的,可见其中的toString方法由于是原生方法,无法用var ele in parent遍历到的。

如果仅仅想覆写父类的某个方法,还可以使用call或者apply尝试一下方法的this大挪移,略。

原型链继承看起来似乎是最自然和最具亲和力的继承方式了,但是还记得上一节中对于单例模式的处理吗?我使用了getInstance方法去取得一个唯一的实例,而不是new,这样原型对其实例化起不到作用了:

  1. var Player = (function(){   
  2.     Player = function(){ //这只是个空壳   
  3.         throw new Error("Can not instantiate a Player object.");   
  4.     };   
  5.     Player.MIN_EXTENDED_TIME = 1;   
  6.     Player.MAX_EXTENDED_TIME = 3;   
  7.     Player._player = false;   
  8.     Player.getInstance = function(){   
  9.         if(!Player._player){   
  10.             alert("Init...");   
  11.             Player._player = {   
  12.                 _name : name,   
  13.                 setName : function(name){   
  14.                     this._name = name;   
  15.                 },   
  16.                 toString : function(name){   
  17.                     return "Player: " + this._name;   
  18.                 }   
  19.             };   
  20.         }   
  21.         return Player._player;   
  22.     };   
  23.     return Player; //把修缮完工的Player这个组件方法返回   
  24. })(); 

现在,我要创建一个WindowsMediaPlayer,去继承上面的Player,怎么做?

这里提供两条思路:

(1)获取Player的实例,然后遍历实例中的方法和属性,构造一个全新的WindowsMediaPlayer,其它的属性照抄Player,但是唯有getInstance方法需要覆写。这个方式不够优雅,而且getInstance方法可能会很复杂和冗余,也许不是一个很好的思路。

(2)从对象设计的角度来说,一个单例的类,本身就不适合被继承,那么,还不如把Player做成一个纯粹的抽象层,让单例这个工作交给其子类WindowMediaPlayer去完成。这个方式要好得多,至于如何把一个function做成一个抽象层。

#p#

重用老代码

在Java中,有这样一段老代码:

  1. class Round{   
  2.     public void drawRound(); //画圆   

现在新代码希望能和它共存,使用一个Person的对象来控制,只不过,可能drawRound,也可能drawRect啊:

  1. class Rect{   
  2.     public void drawRect(); //画方   

好,废话少说,我先想到了Adapter模式:

  1. interface Drawable{   
  2.     public void draw();   
  3. }  
  4.  
  5. public class RoundAdapter implements Drawable{   
  6.     private Round round;   
  7.     public void draw(){   
  8.         round.drawRound();   
  9.     }   
  10. }  
  11.  
  12. public class RectAdapter implements Drawable{   
  13.     private Rect rect;   
  14.     public void draw(){   
  15.         rect.drawRect();   
  16.     }   

然后,我再引入一个Person对象,就能搞定这一切了:

  1. class Person{   
  2.     private Drawable adapter;   
  3.     public Person(Drawable adapter){   
  4.         this.adapter = adapter;   
  5.     }   
  6.     public void draw(){   
  7.         this.adapter.draw();   
  8.     }   
  9. }  
  10.  
  11. Drawable rou = new RoundAdapter();   
  12. Drawable rec = new RectAdapter();   
  13. new Person(rou).draw(); //画圆   
  14. new Person(rec).draw(); //画方 

想必到此已经让你烦了,一个Adapter模式的最简单例子。再多看一看,这个模式的核心是什么?接口!对,正是例子中的Drawable接口——正是在接口的规约和领导下,我们才能让画圆和画方都变得那么听话。

 

现在JavaScript中,也有这样一段老代码:

  1. function Round(){   
  2.     this.drawRound = function(){   
  3.         alert("round");   
  4.     }   

 

我也想依葫芦画瓢,但是JavaScript没有接口了,怎么办?

……

接口的作用是什么?是对类的行为的规约,可是JavaScript的行为是动态的,无法用简单纯粹的接口来实现、来约束,即便模拟出这样一个接口(参见《JavaScript Design Pattern》),在此又有必要使用它么?强行做出一个接口来,这不是和JavaScript的初衷相违背了吗?

再回到这个问题上面,我原本希望Person的对象可以调用一个统一的draw方法,只是在通过构造Person对象的时候,传入一个不同实现的Drawable对象,做出了不同约束下的实现。

那么,JavaScript中,不仅仅方法的调用者可以作为一个参数传入,方法本身也可以作为参数传入(即所谓方法闭包),这样,所有变化点都控制在这个参数之中,不也实现了我想要的接口规约的效果吗:

  1. function Rect(){   
  2.     this.drawRect = function(){   
  3.         alert("rect");   
  4.     }   
  5. }  
  6.  
  7. function Person(obj){   
  8.     //obj参数的格式:{doWhat,who}   
  9.     for(var i in obj){   
  10.         this.doWhat = i;   
  11.         this.who = obj[i];   
  12.         break;   
  13.     }   
  14.     this.draw = function(){   
  15.         this.who[this.doWhat].call(this.who);   
  16.     };   
  17. }  
  18.  
  19. var rou = { drawRound : new Round() };   
  20. var rec = { drawRect : new Rect() };   
  21. (new Person(rou)).draw();   
  22. (new Person(rec)).draw();  
  23.  
  24.   

 

写到这里,我觉得很开心:

在Java中,通过接口的规约和适配器的帮助,我将变化点封装在Person构造器的参数之中;

JavaScript中,没有了接口、脱离了适配器的帮助,我依然能将变化点封装在Person的构造器参数之中。

#p#

JSDoc和JSLint

JSDoc可以生成类似于JavaDoc一样的API文档,这对于前端开发是必不可少的。

下载jsdoc-tookit(http://code.google.com/p/jsdoc-toolkit/)和jsdoc-tookit-ant-task(http://code.google.com/p/jsdoc-toolkit-ant-task/

 

  1. default="build-docs">   
  2.     "build-docs">   
  3.         "base" location="." />   
  4.         "jsdoctoolkit" classname="uk.co.darrenhurley.ant.tasks.JsDocToolkit" classpath="jsdoc-toolkit-ant-task-1.1.0.jar;jsdoc-toolkit\java\classes\js.jar"/>   
  5.         "jsdoc" jsdochome="${base}/jsdoc-toolkit/" outputdir="${base}/output/">   
  6.             "portalone-common.js" />   
  7.            
  8.        
  9.  

其它也有类似的工具,DOC生成器对于任何一个成熟的前端开发团队都是必不可少的。

JavaScript重构

 

JSLint是用来对JavaScript代码做静态检查的工具(http://jslint.com/),不过这个应该不是开源的;而且需要ruby运行环境和gvim,再配合cscript engine,使用起来有诸多不便。项目中不可能总使用在线版本:

JavaScript重构

 

Eclipse上也开发了相应的JSLint plugin,另外,有一个很方便的工具jslint-toolkit(http://code.google.com/p/jslint-toolkit/):

先配置config.json,红色字体就是要检查的js目录:

  1. {   
  2.     // JavaScript files to check   
  3.     //"includes": ["scripts\\source", "scripts\\jquery"],   
  4.     "includes": ["scripts\\my"],   
  5.     // Exclude files   
  6.     "excludes": [],   
  7.     // Exclude file names (Regex expression)   
  8.     "excludeNames": ["\\.svn""CVS"],   
  9.     // Output directory   
  10.     "outPath""out"   

 

输出结果一目了然:

JavaScript重构

原文链接:http://blog.csdn.net/RayChase/archive/2011/05/15/6423039.aspx

【编辑推荐】

  1. 在JavaScript中监听IME键盘输入事件
  2. 19个很有用的JavaScript库强烈推荐
  3. 15款超棒的JavaScript开发工具推荐
  4. 4月超棒的JavaScript游戏开发框架推荐
  5. 从零开始学习jQuery之你必须知道的JavaScript
责任编辑:陈贻新 来源: RayChase的博客
相关推荐

2023-05-12 08:11:58

JavaScriptJSON克隆

2010-10-08 12:52:33

Javascriptreplace

2010-10-08 15:17:47

JavaScriptJava

2010-10-08 14:19:34

JavaScript split()

2010-06-03 13:08:51

2009-09-27 17:13:36

Hibernate V

2009-07-06 10:44:45

JSP charset

2020-06-10 08:37:21

JavaScript重构技巧

2010-05-25 12:59:00

Subversion

2009-09-14 15:12:40

LINQ to XML

2010-08-04 13:52:53

Flex事件机制

2010-06-30 16:00:01

FTP协议

2010-06-12 14:35:46

UML对象图

2010-05-27 10:23:01

SVN文档

2009-09-25 09:36:55

Hibernate核心

2010-05-27 12:58:07

SVN升级

2010-06-17 14:35:03

设计模式 UML

2009-09-28 14:54:33

Hibernate映射

2010-01-26 17:53:30

Android代码结构

2011-07-11 17:38:42

JAVA
点赞
收藏

51CTO技术栈公众号