中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行JavaScript重構的分析

發布時間:2021-11-17 16:46:06 來源:億速云 閱讀:153 作者:柒染 欄目:web開發

本篇文章為大家展示了如何進行JavaScript重構的分析,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

通常我們的團隊中,開發人員在Java語言層面具備相當的技術素養,經驗豐富,而且有許多成熟的、合理的規約,類型繁多的代碼隱患檢查工具,甚至在團隊間還有計劃內的評審和飛檢。但是前端的代碼不似后臺,就像一個沒人疼的孩子,不僅僅容易被低估、被輕視,導致質量低劣、可維護性差,技能上,更缺少優秀的前端開發人員。

JavaScript是前臺代碼中重要組成部分,隨著版本的延續,產品越做越大,JavaScript層面的重構,需要在整個過程中逐步強化起來。

當代碼量達到一定程度,JavaScript最好能夠與頁面模塊組件(例如自定義的FreeMarker標簽)一起被模塊化。

模塊化帶來的最大好處就是獨立性和可維護性,不用在海量的js中定位問題位置,簡單了,也就更容易被理解和接受,更容易被定制。

模塊之間的依賴關系最好能夠保持簡單,例如有一個common.js,成為最通用的函數型代碼,不包含或者包含統一管理的全局變量,要求其可以獨立發布,其他組件js可以輕松地依賴于它。舉個例子,我們經常需要對字符串實現一個trim方法,可是js本身是不具備的,那么就可以在這個common.js中擴展string的prototype來實現,這對外部的使用者是透明的。

模塊劃分和命名空間

使用命名空間是保持js互不干擾的一個好辦法,js講究起面向對象,就必須遵循封裝、繼承和多態的原則。

參照Java import的用法,我希望命名空間能帶來這樣的效果,看一個最簡單的實例吧:

我有一個模塊play,其中包含了一個方法webOnlinePlay,那么在沒有import這個模塊的時候,我希望是js的執行是錯誤的:

webOnlinePlay(); //Error! 無法找到方法

但是如果我引入了這個模塊:

import("play");  webOnlinePlay(); //正確,能夠找到方法

其實實現這樣的效果也很簡單,因為默認調用一個方法webOnlinePlay()的實質是:window.webOnlinePlay(),對嗎?

所以在import("play")的時候,內部實現機制如下:

var module = new playModule();

對于這個模塊中的每一個方法,都導入到window對象上面,以直接使用:

window[methodName] = module[methodName];

其實這里并沒有什么玄機,但是這種即需即取的思想卻給前端重構帶來了一個思路,一個封裝帶來的可維護性增強的思路,不是嗎?

聰明的你也許還會提到一個問題:

如果我沒有import這個play模塊,這個頁面都不需要,那我能否連這個play.js都不加載呢?

當然可以,請關注下一頁———關于js的動態加載的部分。

JavaScript的動態加載

前一節留下了一個問題,如果JS分門別類也清晰了,那我現在需要在必要的情況下才加載某一模塊的JS,這個怎么實現呢?

方法一,最簡單也是最容易被接受的方法,通過后臺代碼來控制,還是少些復雜的JS吧,通過一個標簽、一個分支判斷,就可以做到,何樂而不為呢?

方法二,如果要使用純JS來控制,那么看看這樣如何:

$.ajax(){       url:"xxx/play.js";       ……       success:function(res){           eval(res.responseText);       }   }

原理是很簡單,不過有一個藏匿著的魔鬼:eval,js加載的生效就靠它了,那么執行的上下文就在它的里面,這就會帶來一些潛在的問題,而且,調試也變得困難。

方法三,通過添加<script>標簽的方式來動態引入腳本:

原理相信大家也馬上能領悟個大概了,需要的時候動態地往頁面的<head>里面寫一對<script>標簽,讓瀏覽器自己去取需要的js,這樣的就解決了方法二里面的魔鬼eval的問題,是一個比較好的方法:

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

這里啰嗦一句,<script>標簽中的src——本質上不就是對src所表示的地址發送一個get請求嗎?這雖然看起來有點歪門邪道,卻恰恰是一個跨域問題的解決辦法!

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下才支持,現在好了,在測試代碼里盡情打吧。

千言萬語不及一個例子:

<script language="javascript" src="jsUnitCore.js"></script>   <script language="javascript" src="play.js"></script> //模塊JS       function testWithMainProcess() {           assertEquals("Web play url", "##http://...##", webOnlinePlay());       }

項目的代碼里到處是Ajax調用,要做單元測試,看來打樁是不可避免了。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" }});

這個樁正是mock了一個假的ajax jason返回:[feed:[entry:"data response"]],看看,使用就和以前接觸過的EasyMock差不多嘛。

對于JavaScript測試框架感興趣的同學還可以了解一些其他的測試框架,例如JSpec。
單元測試代碼建議就放在模塊的包內:test.html,即便理想狀況下,模塊單獨發布時,也是伴隨著測試用例的可靠的前端代碼。

從哪些JavaScript代碼開始做?

1、函數式的代碼。這樣的代碼保證獨立性好,也不需要打什么樁,測試成本低,如果不明白函數式的代碼的含義,請參見“函數式編程”。

2、復雜的邏輯。

是否嘗試TDD?不建議在我們團隊內部使用,前端TDD需要更高的技巧,對人的因素要求更高。如果有一天,后臺Java代碼的TDD做好了,那么換成JavaScript的代碼,沒有本質區別。

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

JavaScript編碼規則

沒有規矩,不成方圓,JavaScript帶來了靈活性,也帶來了不受控的變量和訪問,所以要用規則限制它。一支成熟的團隊,還是一支新鮮的團隊,規則應當是不一樣的,我只是列出一些常見的或者有效的辦法,來約束跳躍的開發人員,思維可以任意飛躍,代碼卻要持續受控。當然,任何規則都是建立在一定的認知基礎之上的,面向對象JavaScript的基礎是必備的,否則一切無從談起。

變量和方法控制:

模塊開發不允許存放獨立的全局變量、全局方法,只允許把變量和方法放置到相應模塊的“命名空間”中,對此的解釋請參見此文。實在心癢了,那么使用匿名函數如何?

(function() {       var value = 'xxx';       var func = function() {...};   })();

模塊化需要嚴格控制住代碼的區域性,這不僅僅是代碼可維護性、可定制性的一方面,同時也讓JavaScript引擎在屬性和方法使用完畢后及時地回收掉。

不允許在模塊代碼中污染原生對象,例如

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

如此的代碼必須集中控制,例如統一放置在common.js中,嚴格保護起來。

數據存放約束:

普通變量、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內部的屬性和私有方法使用下劃線開頭,這樣很好地實現了封裝(上述代碼中如果使用t.instanceNum,是無法訪問到這個值的),如果這段代碼都看不懂的話,趕緊溫習一下JavaScript的面向對象吧 :)。JavaScript中提供了閉包和原型兩種辦法來實現繼承和多態,關于重構中應用這一點,后續的章節我再啰嗦吧。

另外,優先使用JavaScript的原生對象和容器,比如Array,Ajax的數據類型統一切到JSON上來,盡量不要使用隱藏域;另外,通常是不允許隨意擴展DOM對象的。

至于模塊間的通信:模塊間的通信意味著模塊間的耦合性,是需要嚴格避免的;通信的途徑通常使用方法級屬性或者模塊級的prototype變量。

DOM操縱規則:

在模塊代碼中,通常要求把對DOM的操縱獨立到模塊js中,應當避免在DOM模型上顯示地寫時間觸發函數,例如:

<div onclick="xxx" />

借助JQuery基于bind的一系列方法,把行為邏輯獨立出來以后,完全可以看到清爽的HTML標簽。

DOM對象的訪問通常使用id來查找,偶有根據name來查找的,過多次數地、不合理地遍歷DOM樹是前端性能保持的大忌。

CSS的樣式控制:

(1)盡量拒絕的寫法,主要目的是將樣式統一到主題樣式表單中,當然主題樣式表單也是按模塊存放的,對于不同語種的定制和不同風格的切換帶來便利。

(2)規約JavaScript對樣式的操縱,理想狀況下,封裝性好的UI可以自由地替換它的樣式集合。

以上只能算冰山一角,拋磚引玉,實際項目中需要在開發過程中逐步細化和完善。

利用原型和閉包,完成組件方法

終于要定義一個組件方法了,利用原型來實現。看看這樣如何:

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);

恩,有封裝、有常量、也有復寫了Object的toString方法,至于繼承之類的事情,咱們后面再說,初看看還不錯。可是這樣的組件方法定義不夠優雅,也不夠直觀,方法都是放在獨立的位置定義的,并沒有和最開始的組件方法放置在一起,如果能像Java那樣定義豈不更好?

對了,可以用閉包來實現。試試看吧:

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里面,閉包做了很大程度上的強化,包括新的語法的支持;JavaScript的閉包是很簡單的閉包,它沒有特殊的需要額外學習的語法,任意一個function,里面只要包含未綁定變量,這些變量是在function所屬的上下文環境中定義的,那么,這個function就是閉包。順便羅嗦一句,和閉包相反的,不正是不包含任何未綁定變量的函數式代碼嗎?

寫是寫好了,可是轉念一想,Player應當只有一份,它是單例的,最好我也能像Java那樣弄一個單例模式出來 :),可是事不遂愿,我沒有辦法在JavaScript做一個private的構造器,用這種思路去實現單例模式似乎不可行……

怎么辦?

然而天無絕人之路,我控制不了你new一個Player的對象,我卻可以控制你new出來的這個Player對象的屬性和行為!當你需要使用你new出來的Player的對象的時候,你發現根本無法完成,或者它只是一個空殼!真正的東西還是要靠單例中經典的getInstance方法來獲得:

function Player(){       throw new Error("Can not instantiate a Player object.");   }; //這只是個空殼   (function(){ //這才是貨真價實的東西       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()會拋出異常   var player1 = Player.getInstance();   var player2 = Player.getInstance();   player2.setName("RealPlayer");   alert(player2.toString()); //輸出RealPlayer

好,真不錯,單例模式在JavaScript下也成功實施了——你要膽敢new Player();就會拋出一個異常,這樣什么也得不到,只有用getInstance方法得到的對象才是真真正正的Player對象。上面的代碼整個執行的結果,只彈出了一次"Init..."的對話框,說明真正的“構造器邏輯”只調用了一次。

都做到這份上了,依然有小小的遺憾,Player的定義依然被拆分成了兩部分,一部分定義空殼,一部分是一個匿名函數來定義Player的常量和getInstance方法。這兩部分就不能合二為一么?

能。只需要用到一個小小的匿名函數,如果耐心從頭看到這里,也一定能理解:

var Player = (function(){       Player = function(){ //這只是個空殼           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這個組件方法返回   })();   //var player = new Player(); //new Player()會拋出異常   var player1 = Player.getInstance();   var player2 = Player.getInstance();   player2.setName("RealPlayer");   alert(player2.toString()); //輸出RealPlayer

到此,終于如釋重負,深入理解JavaScript面向對象,用好原型和閉包這兩把鋒利的武器,才能寫出優秀的前端代碼來。

利用繼承來做事

終于要說到JavaScript的繼承了,原型鏈繼承是最常用的一種方式:

function Video(){};   function Movie(){};   Movie.prototype = new Video();   Movie.prototype.constructor = Movie; //不要丟失構造器

啰嗦一句,如果我拿到的是方法的實例,一樣可以做繼承:

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不包含該屬性或者方法的時候,才會拷貝parent的一份               child[ele] = parent[ele];                  }   })(video,movie); //匿名函數調用的方式        alert(movie.size); //3   alert(movie.toString()); //[object Object]   alert(movie.getName()); //VideoXXX

可是這種方法是不純粹繼承的,可見其中的toString方法由于是原生方法,無法用var ele in parent遍歷到的。

如果僅僅想覆寫父類的某個方法,還可以使用call或者apply嘗試一下方法的this大挪移,略。

原型鏈繼承看起來似乎是最自然和最具親和力的繼承方式了,但是還記得上一節中對于單例模式的處理嗎?我使用了getInstance方法去取得一個唯一的實例,而不是new,這樣原型對其實例化起不到作用了:

var Player = (function(){       Player = function(){ //這只是個空殼           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這個組件方法返回   })();

現在,我要創建一個WindowsMediaPlayer,去繼承上面的Player,怎么做?

這里提供兩條思路:

(1)獲取Player的實例,然后遍歷實例中的方法和屬性,構造一個全新的WindowsMediaPlayer,其它的屬性照抄Player,但是唯有getInstance方法需要覆寫。這個方式不夠優雅,而且getInstance方法可能會很復雜和冗余,也許不是一個很好的思路。

(2)從對象設計的角度來說,一個單例的類,本身就不適合被繼承,那么,還不如把Player做成一個純粹的抽象層,讓單例這個工作交給其子類WindowMediaPlayer去完成。這個方式要好得多,至于如何把一個function做成一個抽象層。

重用老代碼

在Java中,有這樣一段老代碼:

class Round{       public void drawRound(); //畫圓   }

現在新代碼希望能和它共存,使用一個Person的對象來控制,只不過,可能drawRound,也可能drawRect啊:

class Rect{       public void drawRect(); //畫方   }

好,廢話少說,我先想到了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();       }   }

然后,我再引入一個Person對象,就能搞定這一切了:

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(); //畫方

想必到此已經讓你煩了,一個Adapter模式的最簡單例子。再多看一看,這個模式的核心是什么?接口!對,正是例子中的Drawable接口——正是在接口的規約和領導下,我們才能讓畫圓和畫方都變得那么聽話。

現在JavaScript中,也有這樣一段老代碼:

function Round(){       this.drawRound = function(){           alert("round");       }   }

我也想依葫蘆畫瓢,但是JavaScript沒有接口了,怎么辦?

……

接口的作用是什么?是對類的行為的規約,可是JavaScript的行為是動態的,無法用簡單純粹的接口來實現、來約束,即便模擬出這樣一個接口(參見《JavaScript Design Pattern》),在此又有必要使用它么?強行做出一個接口來,這不是和JavaScript的初衷相違背了嗎?

再回到這個問題上面,我原本希望Person的對象可以調用一個統一的draw方法,只是在通過構造Person對象的時候,傳入一個不同實現的Drawable對象,做出了不同約束下的實現。

那么,JavaScript中,不僅僅方法的調用者可以作為一個參數傳入,方法本身也可以作為參數傳入(即所謂方法閉包),這樣,所有變化點都控制在這個參數之中,不也實現了我想要的接口規約的效果嗎:

function Rect(){       this.drawRect = function(){           alert("rect");       }   }   function Person(obj){       //obj參數的格式:{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中,通過接口的規約和適配器的幫助,我將變化點封裝在Person構造器的參數之中;

JavaScript中,沒有了接口、脫離了適配器的幫助,我依然能將變化點封裝在Person的構造器參數之中。

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/)

<PROJECT < span>default="build-docs">       <TARGET NAME=< span>"build-docs">           <PROPERTY NAME=< span>"base" location="." />           <TASKDEF NAME=< span>"jsdoctoolkit" classname="uk.co.darrenhurley.ant.tasks.JsDocToolkit" classpath="jsdoc-toolkit-ant-task-1.1.0.jar;jsdoc-toolkit\java\classes\js.jar"/>           <JSDOCTOOLKIT TEMPLATE=< span>"jsdoc" jsdochome="${base}/jsdoc-toolkit/" outputdir="${base}/output/">               <SOURCE FILE=< span>"portalone-common.js" />

其它也有類似的工具,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目錄:

{       // 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"   }

輸出結果一目了然:

如何進行JavaScript重構的分析

上述內容就是如何進行JavaScript重構的分析,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

南涧| 黄冈市| 宝兴县| 陈巴尔虎旗| 洪雅县| 曲靖市| 大足县| 马山县| 长丰县| 库伦旗| 丹棱县| 赤峰市| 十堰市| 裕民县| 莒南县| 丽江市| 庆城县| 和龙市| 禄丰县| 包头市| 友谊县| 吉水县| 成都市| 格尔木市| 浦城县| 谷城县| 璧山县| 岳阳市| 怀化市| 济源市| 淮滨县| 浦东新区| 南汇区| 温宿县| 辰溪县| 抚松县| 子长县| 陆川县| 苍南县| 凤冈县| 潍坊市|