如果以后公司再能讓我獨立做一套新的完整系統,那么我肯定會為這個系統再寫一個前端框架,那么我到底該如何寫這個框架呢?
在我以前的博客里我給大家展示了一個我自己寫的框架,由于當時時間很緊張,做之前幾乎沒有完整的思考過我到底該如何去寫這個框架,所以事后對于這個框架我有很多遺憾之處,當我重構過一次代碼后我就沒再做過任何重構操作的工作,因為我根本不想再去給它修修補補了,之所以有這個想法,就是我對我寫的那個框架的基礎架構不滿意。
為什么不滿意這個基礎架構了?我們先來看看我當時封裝框架的方式:
(function(window,document){ var o1 ={},o2={}; var outerObj = { o1:o1, o2:o2 } window.outerObj = outerObj; })(window,document,undefined)
這段代碼是我參考jQuery的編寫方式,現在回頭看看,這個編寫方式無非就是用匿名函數把內部代碼保護起來,除此之外真的沒有用到什么javascript語言其他特性。不過當時這么寫還是有我自己的考慮的,主要是從下面這三個角度思考:
- 要提高網站性能就得盡量減少頁面加載時候的請求個數,因此外部的javascript文件越少越好;
- 單個請求的大小也要盡量最少,因此頁面里javascript代碼要盡量精簡;
- 要向jQuery學習,在頁面里編寫的代碼應該有固定的套路,只要是兩個頁面都會使用javascript代碼都應該遷移到自己編寫的javascript庫里,其次,頁面的javascript開發里容易出錯的代碼都應該由javascript庫來完成
因此我開發javascript庫的時候將大量代碼都遷移到一個外部文件里,這些代碼有的是基礎性的代碼,例如一些工具類,grid的構建代碼,遮罩功能等,還有些代碼是業務代碼例如:加密解密等,這些都集中在了一個外部文件,由于自己原框架的結構只是將這些代碼通過匿名函數包裝起來,而沒有讓基礎性代碼和業務代碼解耦的方式,所以當時編寫的javascript庫就是一個大雜燴,很多東西交織在一起,這使得自己維護代碼的時候不是很科學了,經常變成硬編碼。
總而言之,我之前的javascript庫的基礎結構沒有很好的擴展性和伸縮性,它沒有將不同類型代碼隔離出來的能力,所以我需要一個新的javascript基礎框架,這樣我以后再去開發一個javascript庫,這個基礎架構使得庫更加的健壯。
我最近做了一些前端的項目,這個項目里單個頁面都是非常復雜的,功能非常多,單個頁面的javascript業務代碼少則幾百行,多則上千行,因此我們前端應用里有一個很大的公共庫,但是到了每個頁面又得單獨寫一個外部javascript文件做相應的業務處理,下面是這個項目javascript代碼書寫的基本結構:
var opts = { version:"1.0.0", name:"sharpxiajun" }; (function(opts){ function Clazz(){ return this.init(arguments); } Clazz.fn = Clazz.prototype = { init:function(opts){ this.settings = opts; return this; }, testInit:function(){ // 直接打印對象 console.log(this.settings); // 遍歷對象輸出 for (var o in this.settings){ console.log(this.settings[o]); } return this; } } window.$ = new Clazz(opts) })(opts); // 測試 $.testInit();
運行結果如下所示:
不管我們使用外部公共類庫,還是每個頁面對應的javascript外部文件,都采用這個結構,仔細分析下這個代碼,它和我之前寫的javascript庫的結構并沒有高明之處,只不過我之前的javascript庫是直接使用javascript的對象,而這個結構無非換成了面向對象的寫法,而這個面向對象其實是不好使用繼承的面向對象,是個孤立的對象,因此實際開發里我們要區別不同的功能模塊,只得新建多個不同的功能對象,最后整個應用里會有N多個相互獨立的功能對象,這其實和我原來庫的寫法有著同樣的不易擴展的問題。而且這個代碼有個讓我開發時候很不習慣的問題,就是在具體頁面開發里,我必須先要構建一個參數對象,并把對象傳到外部文件的接口里,如果你沒有提前構造參數對象,那么外部javascript代碼就會出錯。
不過這兩種寫法的差異讓我對編寫javascript庫有了新的想法,這個想法具體內容如下:
一個web前端應用里,排除一些公用的庫例如我必用的jQuery,可能還有時間控件的庫(我以后做web前端估計都會盡量讓我的外部庫最少,像jQuery,requireJs,seajs這樣的庫我基本是毫無保留的使用,像什么eaysui,jqgrid,extjs這樣的庫我會想盡辦法舍棄),其他的javascript代碼都是程序員要自己編寫的,程序員自己寫的代碼不管它是業務代碼還是通用代碼都應該是一個整體,這個整體的表現就是它們都可以用一個對象進行輸出,看看我上面講的兩種寫庫的基礎結構,它們的共同問題要么就是通用代碼和業務代碼交織,耦合,要么就是相互獨立,關系僵硬。
此外,生產上能把javascript合并成少量文件也是非常重要的,上面的第二種庫的寫法(把頁面的業務代碼抽取到外部文件)目的之一就是讓文件合并比較容易,但是這種堆砌式的合并文件總讓我感覺有點不是很舒服的味道。我覺得對于復雜頁面單獨一個javascript外部文件有很多好處,這個做法還是不能舍棄的,但是這個外部文件最好和超大的公共庫有一個邏輯關系,這個關系最好像jQuery原始庫和它的插件之間的關系,如果有這樣的關系我們再合并文件,這個做法感覺就會好多了。
最后,我自己寫庫的做法沒有使用面向對象編程,使用的是javascript對象本身的特點,這個如果換到面向對象編程里就是類的靜態變量方案,而另外一種寫法則是實例化對象的實現方案,我覺得這兩種寫法都有可取之處,也有不足地方,最好我們設計的庫應該兼容這兩個機制。
下面就是我根據上面思考寫的新的基礎javascript基礎框架模型,代碼如下:
(function(window,document){ function Clazz(){ return this.init(arguments); } Clazz.fn = Clazz.prototype = { init:function(opts){ this.settings = opts; return this; }, testInit:function(){ // 直接打印對象 console.log(this.settings); // 遍歷對象輸出 for (var o in this.settings){ console.log(this.settings[o]); } return this; } }; Clazz.addStaticMethod = function(nmSpace,obj,ftn){ if (!Clazz[nmSpace]){Clazz[nmSpace] = {}} for (var i in obj){ Clazz[nmSpace][i] = obj[i]; } if (ftn) {ftn()} } Clazz.addObjectMethod = function(nmSpace,obj,ftn){ if (!Clazz.fn[nmSpace]){Clazz.fn[nmSpace] = {}} for (var i in obj){ Clazz.fn[nmSpace][i] = obj[i]; } if (ftn) {ftn()} } window.Clazz = Clazz; })(window,document,undefined) var opts = { version:"1.0.0", name:"sharpxiajun" }; Clazz.addStaticMethod("myStatic",{ sClz:"static", staticFtn:function(){ console.log(Clazz["myStatic"].sClz); } },function(){ console.log("Add Static Method End!!!!!!!"); }) Clazz.addObjectMethod("myFirst",{ sParam:"sharp", ftn01:function(s){ this.sParam = s; return this; }, ftn02:function(){ console.log("sParam:" + this.sParam); return this; } },function(){ console.log("Add Object Method End!!!!!!!"); }) var $ = new Clazz(opts); // 測試一 $.testInit(); // 測試二 console.log($.myFirst.sParam); $.myFirst.ftn01("My God!!!").ftn02(); // 測試三 console.log(Clazz.myStatic.sClz); Clazz.myStatic.staticFtn();
頁面運行結果如下所示:
這個代碼里我在匿名函數里返回的是類(javascript里其實沒有類的概念,但是有構造函數,所以javascript里的構造函數承擔了類的作用,所以這里提javascript里的類應該是沒問題的),而不是已經實例化好的對象。這樣返回的東西既可以有靜態的方法和屬性又有屬于對象的方法和屬性了。
該結構里有一個Clazz.addStaticMethod方法,它的作用是給定義的類添加靜態的方法,這個方法我設計了三個參數,第一個參數是個字符串,業務函數就是靜態方法的作用域,看下面實例代碼:
console.log(Clazz.myStatic.sClz);
Clazz.myStatic.staticFtn();
這樣就等于給靜態變量一個保護,有人會問如果我們不傳第一個參數怎么辦?認真的童鞋注意,現在我給出的代碼只是想表達我的想法,到了生產實現時候該方法會更加豐滿點,那時如果用戶不傳作用域字段,那么添加的靜態方法和屬性就是直接屬于類本身的。
第二個參數就是具體要添加的靜態屬性和靜態方法,這里我用的是對象類型。
第三個參數是個回調函數,當靜態方式添加成功后調用,當然用戶也可以不傳,加個回調函數參數,是我覺得在設計javascript方法時候都應該給一個回調函數,這就是在運用事件驅動編程的思想,這其實為自己留有余地,這么做常常會在你意想不到的時候發揮重要作用。
方法Clazz.addObjectMethod是給對象添加方法和屬性的,這些方法和屬性石賦予給原型對象的,參數類型和addStaticMethod相同,這里就不在累述了。
用戶在使用addObjectMethod方法時候要注意this指針的運用,在每個要添加到對象的方法最后加上一個return this,這么做好處就是每個對象的返回值都是對象本身,這樣我們就可以讓我們的庫擁有和jQuery一樣的連綴寫法,例如下面的代碼:
console.log($.myFirst.sParam);
$.myFirst.ftn01("My God!!!").ftn02();
在生產開發里,我們可以把公共的javascript代碼直接寫到庫里,頁面里的業務代碼則直接通過擴展返回類的靜態方法和屬性以及對象方法和屬性進行擴充。
上面這個結構基本達到我的需要了,如果我以后用javascriptMVC思想開發前端我估計這個基本結構已經夠用,當然還要做些代碼健壯性的處理,如果是傳統前端頁面開發估計還會有一定修改,傳統網站的頁面開發都是服務端和html混搭的,例如jsp,velocity文件,因此有很多重要數據都是服務端變量生成的,我們時常要把這些變量作為參數傳給外部javascript文件,每個頁面里的參數是不一樣的,所以必須有個對象專門接收這個對象。這個好做,因此這里就不累述了。
上面這個結構使用了面向對象繼承機制,原始庫是父對象,而業務javascript文件則是子類了,不過這個子類是用命名空間來區分的。
不過有時候我們可能想冒險替換整個父對象的內容,這個需求我想在平時開發里并不常見,不過我還是寫了一個這樣的方法,下面是我改進的代碼,具體如下:
(function(window,document){ function Clazz(){ return this._init(arguments); } Clazz.prototype = { _init:function(opts){ this.settings = opts; return this; }, testInit:function(){ // 直接打印對象 console.log(this.settings); // 遍歷對象輸出 for (var o in this.settings){ console.log(this.settings[o]); } return this; } }; Clazz.addStaticMethod = function(nmSpace,obj,ftn){ if (!Clazz[nmSpace]){Clazz[nmSpace] = {}} if (Object.prototype.toString.apply(obj) == "[object Object]"){ for (var i in obj){ Clazz[nmSpace][i] = obj[i]; } window.Clazz = Clazz; } if (ftn) {ftn()} } Clazz.addObjectMethod = function(nmSpace,obj,ftn){ if (!Clazz.prototype[nmSpace]){Clazz.prototype[nmSpace] = {}} if (Object.prototype.toString.apply(obj) == "[object Object]"){ for (var i in obj){ Clazz.prototype[nmSpace][i] = obj[i]; } window.Clazz = Clazz; } if (ftn) {ftn()} } /*Clazz.newStaticMethod(){//todo..........}*/ Clazz.newObjectMethod = function(obj,ftn){ if (Object.prototype.toString.apply(obj) == "[object Object]"){ var tmpInit = Clazz.prototype._init; Clazz.prototype = obj; Clazz.prototype._init = tmpInit; window.Clazz = Clazz; } if (ftn) {ftn()} } window.Clazz = Clazz; })(window,document,undefined) var opts = { version:"1.0.0", name:"sharpxiajun" }; Clazz.addStaticMethod("myStatic",{ sClz:"static", staticFtn:function(){ console.log(Clazz["myStatic"].sClz); } },function(){ console.log("Add Static Method End!!!!!!!"); }) Clazz.myStatic.staticFtn(); Clazz.newObjectMethod({ newver:"1.0.3", testNewFtn:function(){ console.log(this.newver); return this; } },function(){ console.log("New Create Prototype Object End !!!!!!"); }); Clazz.addObjectMethod("myFirst",{ sParam:"sharp", ftn01:function(s){ this.sParam = s; return this; }, ftn02:function(){ console.log("sParam:" + this.sParam); return this; } },function(){ console.log("Add Object Method End!!!!!!!"); }) var $ = new Clazz(); console.log("================================"); console.log($.newver); $.testNewFtn().myFirst.ftn01("XXXX").ftn02(); console.log("================================");
運行結果如下所示:
上面的代碼做了一定優化,代碼里我只給出了替換原型的方法,沒有提供替換靜態的方法,這是因為替換原型還是有點意義的,替換靜態變量其實就是替換整個類了,如果這么干,我這個結構還有啥意義哦。
文章寫畢,今年寫文章,文章里代碼都很少,很多童鞋不習慣,今天補上一篇代碼比較多的文章了。
文章列表