JavaScript重構(gòu)深入剖析
通常我們的團(tuán)隊(duì)中,開發(fā)人員在Java語(yǔ)言層面具備相當(dāng)?shù)募夹g(shù)素養(yǎng),經(jīng)驗(yàn)豐富,而且有許多成熟的、合理的規(guī)約,類型繁多的代碼隱患檢查工具,甚至在團(tuán)隊(duì)間還有計(jì)劃內(nèi)的評(píng)審和飛檢。但是前端的代碼不似后臺(tái),就像一個(gè)沒人疼的孩子,不僅僅容易被低估、被輕視,導(dǎo)致質(zhì)量低劣、可維護(hù)性差,技能上,更缺少優(yōu)秀的前端開發(fā)人員。
JavaScript是前臺(tái)代碼中重要組成部分,隨著版本的延續(xù),產(chǎn)品越做越大,JavaScript層面的重構(gòu),需要在整個(gè)過程中逐步強(qiáng)化起來(lái)。
當(dāng)代碼量達(dá)到一定程度,JavaScript最好能夠與頁(yè)面模塊組件(例如自定義的FreeMarker標(biāo)簽)一起被模塊化。
模塊化帶來(lái)的最大好處就是獨(dú)立性和可維護(hù)性,不用在海量的js中定位問題位置,簡(jiǎn)單了,也就更容易被理解和接受,更容易被定制。
模塊之間的依賴關(guān)系最好能夠保持簡(jiǎn)單,例如有一個(gè)common.js,成為最通用的函數(shù)型代碼,不包含或者包含統(tǒng)一管理的全局變量,要求其可以獨(dú)立發(fā)布,其他組件js可以輕松地依賴于它。舉個(gè)例子,我們經(jīng)常需要對(duì)字符串實(shí)現(xiàn)一個(gè)trim方法,可是js本身是不具備的,那么就可以在這個(gè)common.js中擴(kuò)展string的prototype來(lái)實(shí)現(xiàn),這對(duì)外部的使用者是透明的。
模塊劃分和命名空間
使用命名空間是保持js互不干擾的一個(gè)好辦法,js講究起面向?qū)ο?,就必須遵循封裝、繼承和多態(tài)的原則。
參照J(rèn)ava import的用法,我希望命名空間能帶來(lái)這樣的效果,看一個(gè)最簡(jiǎn)單的實(shí)例吧:
我有一個(gè)模塊play,其中包含了一個(gè)方法webOnlinePlay,那么在沒有import這個(gè)模塊的時(shí)候,我希望是js的執(zhí)行是錯(cuò)誤的:
- webOnlinePlay(); //Error! 無(wú)法找到方法
但是如果我引入了這個(gè)模塊:
- import("play");
- webOnlinePlay(); //正確,能夠找到方法
其實(shí)實(shí)現(xiàn)這樣的效果也很簡(jiǎn)單,因?yàn)槟J(rèn)調(diào)用一個(gè)方法webOnlinePlay()的實(shí)質(zhì)是:window.webOnlinePlay(),對(duì)嗎?
所以在import("play")的時(shí)候,內(nèi)部實(shí)現(xiàn)機(jī)制如下:
- var module = new playModule();
對(duì)于這個(gè)模塊中的每一個(gè)方法,都導(dǎo)入到window對(duì)象上面,以直接使用:
- window[methodName] = module[methodName];
其實(shí)這里并沒有什么玄機(jī),但是這種即需即取的思想?yún)s給前端重構(gòu)帶來(lái)了一個(gè)思路,一個(gè)封裝帶來(lái)的可維護(hù)性增強(qiáng)的思路,不是嗎?
聰明的你也許還會(huì)提到一個(gè)問題:
如果我沒有import這個(gè)play模塊,這個(gè)頁(yè)面都不需要,那我能否連這個(gè)play.js都不加載呢?
當(dāng)然可以,請(qǐng)關(guān)注下一頁(yè)———關(guān)于js的動(dòng)態(tài)加載的部分。
#p#
JavaScript的動(dòng)態(tài)加載
前一節(jié)留下了一個(gè)問題,如果JS分門別類也清晰了,那我現(xiàn)在需要在必要的情況下才加載某一模塊的JS,這個(gè)怎么實(shí)現(xiàn)呢?
方法一,最簡(jiǎn)單也是最容易被接受的方法,通過后臺(tái)代碼來(lái)控制,還是少些復(fù)雜的JS吧,通過一個(gè)標(biāo)簽、一個(gè)分支判斷,就可以做到,何樂而不為呢?
方法二,如果要使用純JS來(lái)控制,那么看看這樣如何:
- $.ajax(){
- url:"xxx/play.js";
- ……
- success:function(res){
- eval(res.responseText);
- }
- }
原理是很簡(jiǎn)單,不過有一個(gè)藏匿著的魔鬼:eval,js加載的生效就靠它了,那么執(zhí)行的上下文就在它的里面,這就會(huì)帶來(lái)一些潛在的問題,而且,調(diào)試也變得困難。
方法三,通過添加<script>標(biāo)簽的方式來(lái)動(dòng)態(tài)引入腳本:
原理相信大家也馬上能領(lǐng)悟個(gè)大概了,需要的時(shí)候動(dòng)態(tài)地往頁(yè)面的<head>里面寫一對(duì)<script>標(biāo)簽,讓瀏覽器自己去取需要的js,這樣的就解決了方法二里面的魔鬼eval的問題,是一個(gè)比較好的方法:
- <script src="xxx/play.js" ... />
這里啰嗦一句,<script>標(biāo)簽中的src——本質(zhì)上不就是對(duì)src所表示的地址發(fā)送一個(gè)get請(qǐng)求嗎?這雖然看起來(lái)有點(diǎn)歪門邪道,卻恰恰是一個(gè)跨域問題的解決辦法!
#p#
JavaScript的測(cè)試
進(jìn)行JavaScript重構(gòu)時(shí),我希望引入易于使用的測(cè)試框架來(lái)保證重構(gòu)的順利進(jìn)行,未來(lái)能持續(xù)通過測(cè)試代碼對(duì)JavaScript邏輯的正確性做保障。
JsUnit (http://sourceforge.net/projects/jsunit/,http://www.jsunit.net/)
JsUnit是一個(gè)獨(dú)立的JavaScript單元測(cè)試框架,和JUnit差不多,沒有上手難度,包括傳統(tǒng)的setUp和tearDown,提供的assert方法也和JUnit類似,多了assertNaN和assertUndefined等等JavaScript特有的方法。測(cè)試頁(yè)面必須在<head>里面引入jsUnitCore.js這個(gè)js文件。
測(cè)試套件的支持:提供了addTestPage和addTestSuite;
測(cè)試日志的支持:包括warn、info和debug三種日志級(jí)別,前端編碼不似后臺(tái)代碼,正式代碼中不宜使用過多l(xiāng)og,再說(shuō)log也只有FF下才支持,現(xiàn)在好了,在測(cè)試代碼里盡情打吧。
千言萬(wàn)語(yǔ)不及一個(gè)例子:
- <script language="javascript" src="jsUnitCore.js"></script>
- <script language="javascript" src="play.js"></script> //模塊JS
- function testWithMainProcess() {
- assertEquals("Web play url", "##http://...##", webOnlinePlay());
- }
項(xiàng)目的代碼里到處是Ajax調(diào)用,要做單元測(cè)試,看來(lái)打樁是不可避免了。Mock類的工具有許多,比如適合JQuery的QMock:
- var mockJquery = new Mock();
- mockJquery
- .expects(1)
- .method('ajax')
- .withArguments({
- url: 'http://xxx,
- success: Function,
- dataType: "jsonp"
- })
- .callFunctionWith({ feed : { entry : "data response" }});
這個(gè)樁正是mock了一個(gè)假的ajax jason返回:[feed:[entry:"data response"]],看看,使用就和以前接觸過的EasyMock差不多嘛。
對(duì)于JavaScript測(cè)試框架感興趣的同學(xué)還可以了解一些其他的測(cè)試框架,例如JSpec。
單元測(cè)試代碼建議就放在模塊的包內(nèi):test.html,即便理想狀況下,模塊單獨(dú)發(fā)布時(shí),也是伴隨著測(cè)試用例的可靠的前端代碼。
從哪些JavaScript代碼開始做?
1、函數(shù)式的代碼。這樣的代碼保證獨(dú)立性好,也不需要打什么樁,測(cè)試成本低,如果不明白函數(shù)式的代碼的含義,請(qǐng)參見“函數(shù)式編程”。
2、復(fù)雜的邏輯。
是否嘗試TDD?不建議在我們團(tuán)隊(duì)內(nèi)部使用,前端TDD需要更高的技巧,對(duì)人的因素要求更高。如果有一天,后臺(tái)Java代碼的TDD做好了,那么換成JavaScript的代碼,沒有本質(zhì)區(qū)別。
如果效果得當(dāng),為什么不能把JavaScript的UT集成到ICP-CI上作為持續(xù)集成的一部分呢?
#p#
JavaScript編碼規(guī)則
沒有規(guī)矩,不成方圓,JavaScript帶來(lái)了靈活性,也帶來(lái)了不受控的變量和訪問,所以要用規(guī)則限制它。一支成熟的團(tuán)隊(duì),還是一支新鮮的團(tuán)隊(duì),規(guī)則應(yīng)當(dāng)是不一樣的,我只是列出一些常見的或者有效的辦法,來(lái)約束跳躍的開發(fā)人員,思維可以任意飛躍,代碼卻要持續(xù)受控。當(dāng)然,任何規(guī)則都是建立在一定的認(rèn)知基礎(chǔ)之上的,面向?qū)ο驤avaScript的基礎(chǔ)是必備的,否則一切無(wú)從談起。
變量和方法控制:
模塊開發(fā)不允許存放獨(dú)立的全局變量、全局方法,只允許把變量和方法放置到相應(yīng)模塊的“命名空間”中,對(duì)此的解釋請(qǐng)參見此文。實(shí)在心癢了,那么使用匿名函數(shù)如何?
- (function() {
- var value = 'xxx';
- var func = function() {...};
- })();
模塊化需要嚴(yán)格控制住代碼的區(qū)域性,這不僅僅是代碼可維護(hù)性、可定制性的一方面,同時(shí)也讓JavaScript引擎在屬性和方法使用完畢后及時(shí)地回收掉。
不允許在模塊代碼中污染原生對(duì)象,例如
- String.prototype.func = new function(){...};
如此的代碼必須集中控制,例如統(tǒng)一放置在common.js中,嚴(yán)格保護(hù)起來(lái)。
數(shù)據(jù)存放約束:
普通變量、prototype變量和function變量分而治之,方法名一律大寫開頭,變量名還是遵從駱駝命名法如何:
- function T(name){
- T.prototype._instance_number++;
- this.name = name;
- this.showName=function(){
- alert(this.name);
- }
- };
- T.prototype = {
- _instance_number:0,
- getInstanceNum: function(){
- return T.prototype._instance_number;
- }
- };
- var t = new T("PortalONE");
- t.showName();
- new T("Again");
- alert(t.getInstanceNum()); //打印:2
這里有意做了一件事情,T內(nèi)部的屬性和私有方法使用下劃線開頭,這樣很好地實(shí)現(xiàn)了封裝(上述代碼中如果使用t.instanceNum,是無(wú)法訪問到這個(gè)值的),如果這段代碼都看不懂的話,趕緊溫習(xí)一下JavaScript的面向?qū)ο蟀?:)。JavaScript中提供了閉包和原型兩種辦法來(lái)實(shí)現(xiàn)繼承和多態(tài),關(guān)于重構(gòu)中應(yīng)用這一點(diǎn),后續(xù)的章節(jié)我再啰嗦吧。
另外,優(yōu)先使用JavaScript的原生對(duì)象和容器,比如Array,Ajax的數(shù)據(jù)類型統(tǒng)一切到JSON上來(lái),盡量不要使用隱藏域;另外,通常是不允許隨意擴(kuò)展DOM對(duì)象的。
至于模塊間的通信:模塊間的通信意味著模塊間的耦合性,是需要嚴(yán)格避免的;通信的途徑通常使用方法級(jí)屬性或者模塊級(jí)的prototype變量。
DOM操縱規(guī)則:
在模塊代碼中,通常要求把對(duì)DOM的操縱獨(dú)立到模塊js中,應(yīng)當(dāng)避免在DOM模型上顯示地寫時(shí)間觸發(fā)函數(shù),例如:
- <div onclick="xxx" />
借助JQuery基于bind的一系列方法,把行為邏輯獨(dú)立出來(lái)以后,完全可以看到清爽的HTML標(biāo)簽。
DOM對(duì)象的訪問通常使用id來(lái)查找,偶有根據(jù)name來(lái)查找的,過多次數(shù)地、不合理地遍歷DOM樹是前端性能保持的大忌。
CSS的樣式控制:
(1)盡量拒絕style="xxx"的寫法,主要目的是將樣式統(tǒng)一到主題樣式表單中,當(dāng)然主題樣式表單也是按模塊存放的,對(duì)于不同語(yǔ)種的定制和不同風(fēng)格的切換帶來(lái)便利。
(2)規(guī)約JavaScript對(duì)樣式的操縱,理想狀況下,封裝性好的UI可以自由地替換它的樣式集合。
以上只能算冰山一角,拋磚引玉,實(shí)際項(xiàng)目中需要在開發(fā)過程中逐步細(xì)化和完善。
#p#
利用原型和閉包,完成組件方法
終于要定義一個(gè)組件方法了,利用原型來(lái)實(shí)現(xiàn)??纯催@樣如何:
- function Player(name){
- Player.MIN_EXTENDED_TIME = 1;
- Player.MAX_EXTENDED_TIME = 3;
- this._name = name;
- };
- Player.prototype.setName = function(name){
- this._name = name;
- };
- Player.prototype.toString = function(){
- return "Player: " + this._name;
- };
- var player = new Player("WindowsMediaPlayer");
- alert(player.toString()); //輸出WindowsMediaPlayer
- player.setName("RealPlayer");
- alert(player.toString()); //輸出RealPlayer
- alert(Player.MAX_EXTENDED_TIME);
恩,有封裝、有常量、也有復(fù)寫了Object的toString方法,至于繼承之類的事情,咱們后面再說(shuō),初看看還不錯(cuò)。可是這樣的組件方法定義不夠優(yōu)雅,也不夠直觀,方法都是放在獨(dú)立的位置定義的,并沒有和最開始的組件方法放置在一起,如果能像Java那樣定義豈不更好?
對(duì)了,可以用閉包來(lái)實(shí)現(xiàn)。試試看吧:
- function Player(name){
- Player.MIN_EXTENDED_TIME = 1;
- Player.MAX_EXTENDED_TIME = 3;
- this._name = name;
- this.setName = function(name){
- this._name = name;
- };
- this.toString = function(){
- return "Player: " + this._name;
- };
- };
- var player = new Player("WindowsMediaPlayer");
- alert(player.toString()); //輸出WindowsMediaPlayer
- player.setName("RealPlayer");
- alert(player.toString()); //輸出RealPlayer
- alert(Player.MAX_EXTENDED_TIME);
不像Groovy里面,閉包做了很大程度上的強(qiáng)化,包括新的語(yǔ)法的支持;JavaScript的閉包是很簡(jiǎn)單的閉包,它沒有特殊的需要額外學(xué)習(xí)的語(yǔ)法,任意一個(gè)function,里面只要包含未綁定變量,這些變量是在function所屬的上下文環(huán)境中定義的,那么,這個(gè)function就是閉包。順便羅嗦一句,和閉包相反的,不正是不包含任何未綁定變量的函數(shù)式代碼嗎?
寫是寫好了,可是轉(zhuǎn)念一想,Player應(yīng)當(dāng)只有一份,它是單例的,最好我也能像Java那樣弄一個(gè)單例模式出來(lái) :),可是事不遂愿,我沒有辦法在JavaScript做一個(gè)private的構(gòu)造器,用這種思路去實(shí)現(xiàn)單例模式似乎不可行……
怎么辦?
然而天無(wú)絕人之路,我控制不了你new一個(gè)Player的對(duì)象,我卻可以控制你new出來(lái)的這個(gè)Player對(duì)象的屬性和行為!當(dāng)你需要使用你new出來(lái)的Player的對(duì)象的時(shí)候,你發(fā)現(xiàn)根本無(wú)法完成,或者它只是一個(gè)空殼!真正的東西還是要靠單例中經(jīng)典的getInstance方法來(lái)獲得:
- function Player(){
- throw new Error("Can not instantiate a Player object.");
- }; //這只是個(gè)空殼
- (function(){ //這才是貨真價(jià)實(shí)的東西
- Player.MIN_EXTENDED_TIME = 1;
- Player.MAX_EXTENDED_TIME = 3;
- Player._player = false;
- Player.getInstance = function(){
- if(!Player._player){
- alert("Init...");
- Player._player = {
- _name : name,
- setName : function(name){
- this._name = name;
- },
- toString : function(name){
- return "Player: " + this._name;
- }
- };
- }
- return Player._player;
- };
- })();
- //var player = new Player(); //new Player()會(huì)拋出異常
- var player1 = Player.getInstance();
- var player2 = Player.getInstance();
- player2.setName("RealPlayer");
- alert(player2.toString()); //輸出RealPlayer
好,真不錯(cuò),單例模式在JavaScript下也成功實(shí)施了——你要膽敢new Player();就會(huì)拋出一個(gè)異常,這樣什么也得不到,只有用getInstance方法得到的對(duì)象才是真真正正的Player對(duì)象。上面的代碼整個(gè)執(zhí)行的結(jié)果,只彈出了一次"Init..."的對(duì)話框,說(shuō)明真正的“構(gòu)造器邏輯”只調(diào)用了一次。
都做到這份上了,依然有小小的遺憾,Player的定義依然被拆分成了兩部分,一部分定義空殼,一部分是一個(gè)匿名函數(shù)來(lái)定義Player的常量和getInstance方法。這兩部分就不能合二為一么?
能。只需要用到一個(gè)小小的匿名函數(shù),如果耐心從頭看到這里,也一定能理解:
- var Player = (function(){
- Player = function(){ //這只是個(gè)空殼
- throw new Error("Can not instantiate a Player object.");
- };
- Player.MIN_EXTENDED_TIME = 1;
- Player.MAX_EXTENDED_TIME = 3;
- Player._player = false;
- Player.getInstance = function(){
- if(!Player._player){
- alert("Init...");
- Player._player = {
- _name : name,
- setName : function(name){
- this._name = name;
- },
- toString : function(name){
- return "Player: " + this._name;
- }
- };
- }
- return Player._player;
- };
- return Player; //把修繕完工的Player這個(gè)組件方法返回
- })();
- //var player = new Player(); //new Player()會(huì)拋出異常
- var player1 = Player.getInstance();
- var player2 = Player.getInstance();
- player2.setName("RealPlayer");
- alert(player2.toString()); //輸出RealPlayer
到此,終于如釋重負(fù),深入理解JavaScript面向?qū)ο?,用好原型和閉包這兩把鋒利的武器,才能寫出優(yōu)秀的前端代碼來(lái)。
#p#
利用繼承來(lái)做事
終于要說(shuō)到JavaScript的繼承了,原型鏈繼承是最常用的一種方式:
- function Video(){};
- function Movie(){};
- Movie.prototype = new Video();
- Movie.prototype.constructor = Movie; //不要丟失構(gòu)造器
啰嗦一句,如果我拿到的是方法的實(shí)例,一樣可以做繼承:
- function Video(){};
- function Movie(){};
- var video = new Video();
- video.size = 3;
- video.toString = function(){
- return "video";
- };
- video.getName = function(){
- return "VideoXXX";
- };
- var movie = new Movie();
- (function inherit(parent,child){
- for(var ele in parent){
- if(!child[ele]) //在child不包含該屬性或者方法的時(shí)候,才會(huì)拷貝parent的一份
- child[ele] = parent[ele];
- }
- })(video,movie); //匿名函數(shù)調(diào)用的方式
- alert(movie.size); //3
- alert(movie.toString()); //[object Object]
- alert(movie.getName()); //VideoXXX
可是這種方法是不純粹繼承的,可見其中的toString方法由于是原生方法,無(wú)法用var ele in parent遍歷到的。
如果僅僅想覆寫父類的某個(gè)方法,還可以使用call或者apply嘗試一下方法的this大挪移,略。
原型鏈繼承看起來(lái)似乎是最自然和最具親和力的繼承方式了,但是還記得上一節(jié)中對(duì)于單例模式的處理嗎?我使用了getInstance方法去取得一個(gè)唯一的實(shí)例,而不是new,這樣原型對(duì)其實(shí)例化起不到作用了:
- var Player = (function(){
- Player = function(){ //這只是個(gè)空殼
- throw new Error("Can not instantiate a Player object.");
- };
- Player.MIN_EXTENDED_TIME = 1;
- Player.MAX_EXTENDED_TIME = 3;
- Player._player = false;
- Player.getInstance = function(){
- if(!Player._player){
- alert("Init...");
- Player._player = {
- _name : name,
- setName : function(name){
- this._name = name;
- },
- toString : function(name){
- return "Player: " + this._name;
- }
- };
- }
- return Player._player;
- };
- return Player; //把修繕完工的Player這個(gè)組件方法返回
- })();
現(xiàn)在,我要?jiǎng)?chuàng)建一個(gè)WindowsMediaPlayer,去繼承上面的Player,怎么做?
這里提供兩條思路:
(1)獲取Player的實(shí)例,然后遍歷實(shí)例中的方法和屬性,構(gòu)造一個(gè)全新的WindowsMediaPlayer,其它的屬性照抄Player,但是唯有g(shù)etInstance方法需要覆寫。這個(gè)方式不夠優(yōu)雅,而且getInstance方法可能會(huì)很復(fù)雜和冗余,也許不是一個(gè)很好的思路。
(2)從對(duì)象設(shè)計(jì)的角度來(lái)說(shuō),一個(gè)單例的類,本身就不適合被繼承,那么,還不如把Player做成一個(gè)純粹的抽象層,讓單例這個(gè)工作交給其子類WindowMediaPlayer去完成。這個(gè)方式要好得多,至于如何把一個(gè)function做成一個(gè)抽象層。
#p#
重用老代碼
在Java中,有這樣一段老代碼:
- class Round{
- public void drawRound(); //畫圓
- }
現(xiàn)在新代碼希望能和它共存,使用一個(gè)Person的對(duì)象來(lái)控制,只不過,可能drawRound,也可能drawRect?。?/P>
- class Rect{
- public void drawRect(); //畫方
- }
好,廢話少說(shuō),我先想到了Adapter模式:
- interface Drawable{
- public void draw();
- }
- public class RoundAdapter implements Drawable{
- private Round round;
- public void draw(){
- round.drawRound();
- }
- }
- public class RectAdapter implements Drawable{
- private Rect rect;
- public void draw(){
- rect.drawRect();
- }
- }
然后,我再引入一個(gè)Person對(duì)象,就能搞定這一切了:
- class Person{
- private Drawable adapter;
- public Person(Drawable adapter){
- this.adapter = adapter;
- }
- public void draw(){
- this.adapter.draw();
- }
- }
- Drawable rou = new RoundAdapter();
- Drawable rec = new RectAdapter();
- new Person(rou).draw(); //畫圓
- new Person(rec).draw(); //畫方
想必到此已經(jīng)讓你煩了,一個(gè)Adapter模式的最簡(jiǎn)單例子。再多看一看,這個(gè)模式的核心是什么?接口!對(duì),正是例子中的Drawable接口——正是在接口的規(guī)約和領(lǐng)導(dǎo)下,我們才能讓畫圓和畫方都變得那么聽話。
現(xiàn)在JavaScript中,也有這樣一段老代碼:
- function Round(){
- this.drawRound = function(){
- alert("round");
- }
- }
我也想依葫蘆畫瓢,但是JavaScript沒有接口了,怎么辦?
……
接口的作用是什么?是對(duì)類的行為的規(guī)約,可是JavaScript的行為是動(dòng)態(tài)的,無(wú)法用簡(jiǎn)單純粹的接口來(lái)實(shí)現(xiàn)、來(lái)約束,即便模擬出這樣一個(gè)接口(參見《JavaScript Design Pattern》),在此又有必要使用它么?強(qiáng)行做出一個(gè)接口來(lái),這不是和JavaScript的初衷相違背了嗎?
再回到這個(gè)問題上面,我原本希望Person的對(duì)象可以調(diào)用一個(gè)統(tǒng)一的draw方法,只是在通過構(gòu)造Person對(duì)象的時(shí)候,傳入一個(gè)不同實(shí)現(xiàn)的Drawable對(duì)象,做出了不同約束下的實(shí)現(xiàn)。
那么,JavaScript中,不僅僅方法的調(diào)用者可以作為一個(gè)參數(shù)傳入,方法本身也可以作為參數(shù)傳入(即所謂方法閉包),這樣,所有變化點(diǎn)都控制在這個(gè)參數(shù)之中,不也實(shí)現(xiàn)了我想要的接口規(guī)約的效果嗎:
- function Rect(){
- this.drawRect = function(){
- alert("rect");
- }
- }
- function Person(obj){
- //obj參數(shù)的格式:{doWhat,who}
- for(var i in obj){
- this.doWhat = i;
- this.who = obj[i];
- break;
- }
- this.draw = function(){
- this.who[this.doWhat].call(this.who);
- };
- }
- var rou = { drawRound : new Round() };
- var rec = { drawRect : new Rect() };
- (new Person(rou)).draw();
- (new Person(rec)).draw();
寫到這里,我覺得很開心:
在Java中,通過接口的規(guī)約和適配器的幫助,我將變化點(diǎn)封裝在Person構(gòu)造器的參數(shù)之中;
JavaScript中,沒有了接口、脫離了適配器的幫助,我依然能將變化點(diǎn)封裝在Person的構(gòu)造器參數(shù)之中。
#p#
JSDoc和JSLint
JSDoc可以生成類似于JavaDoc一樣的API文檔,這對(duì)于前端開發(fā)是必不可少的。
下載jsdoc-tookit(http://code.google.com/p/jsdoc-toolkit/)和jsdoc-tookit-ant-task(http://code.google.com/p/jsdoc-toolkit-ant-task/)
default="build-docs"> "build-docs"> "base" location="." /> "jsdoctoolkit" classname="uk.co.darrenhurley.ant.tasks.JsDocToolkit" classpath="jsdoc-toolkit-ant-task-1.1.0.jar;jsdoc-toolkit\java\classes\js.jar"/> "jsdoc" jsdochome="${base}/jsdoc-toolkit/" outputdir="${base}/output/"> "portalone-common.js" />
其它也有類似的工具,DOC生成器對(duì)于任何一個(gè)成熟的前端開發(fā)團(tuán)隊(duì)都是必不可少的。
JSLint是用來(lái)對(duì)JavaScript代碼做靜態(tài)檢查的工具(http://jslint.com/),不過這個(gè)應(yīng)該不是開源的;而且需要ruby運(yùn)行環(huán)境和gvim,再配合cscript engine,使用起來(lái)有諸多不便。項(xiàng)目中不可能總使用在線版本:
Eclipse上也開發(fā)了相應(yīng)的JSLint plugin,另外,有一個(gè)很方便的工具jslint-toolkit(http://code.google.com/p/jslint-toolkit/):
先配置config.json,紅色字體就是要檢查的js目錄:
- {
- // JavaScript files to check
- //"includes": ["scripts\\source", "scripts\\jquery"],
- "includes": ["scripts\\my"],
- // Exclude files
- "excludes": [],
- // Exclude file names (Regex expression)
- "excludeNames": ["\\.svn", "CVS"],
- // Output directory
- "outPath": "out"
- }
輸出結(jié)果一目了然:
原文鏈接:http://blog.csdn.net/RayChase/archive/2011/05/15/6423039.aspx
【編輯推薦】