如何組織大型JavaScript應(yīng)用中的代碼

2018-06-09 16:11 更新

本文作者Cliff Meyers是一個(gè)前端工程師,熟悉HTML5、JavaScript、J2EE開發(fā),他在開發(fā)過程中總結(jié)了自己在應(yīng)對(duì)JavaScript應(yīng)用越來越龐大情況下的文件結(jié)構(gòu),深得其他開發(fā)者認(rèn)可。

地板上堆放的衣服

首先,我們來看看angular-seed,它是AngularJS應(yīng)用開發(fā)的官方入門項(xiàng)目,其文件結(jié)構(gòu)是這樣的,


- css/
- img/
- js/
    - app.js
    - controller.js
    - directives.js
    - filters.js
    - services.js
- lib/
- partials

看起來就像是把衣服按類型堆在地板上,一堆襪子、一堆內(nèi)衣、一堆襯衫等等。你知道拐角的那堆襪子里有今天要穿的黑色羊毛襪,但你仍需要花上一段時(shí)間來尋找。

這種組織方式很凌亂。一旦你的代碼中存在6、7個(gè)甚至更多的控制器或者服務(wù),文件管理就會(huì)變得難以處理——很難找到想要尋找的對(duì)象,源代碼控制中的文件也變更集變得難懂。

襪子抽屜

常見的JavaScript文件結(jié)構(gòu)還有另一種形式,即按原型將文件分類。我們繼續(xù)用整理衣服來比喻——現(xiàn)在我們買了有很多抽屜的衣柜,打算將襪子放在其中一個(gè)抽屜里,內(nèi)衣放在另一個(gè)抽屜,再把襯衫整齊地疊在第三個(gè)抽屜……

想象一下,我們正在開發(fā)一個(gè)簡單的電子商務(wù)網(wǎng)站,包括登陸流程、產(chǎn)品目錄以及購物車UI。同樣,我們將文件分為以下幾個(gè)原型:models(業(yè)務(wù)邏輯和狀態(tài))、controllers以及services(HTTP/JSON端點(diǎn)加密),而按照Angular默認(rèn)那樣非籠統(tǒng)地歸到service架構(gòu)。

因此我們的JavaScript目錄變成了這樣,


- controllers/
    - LoginController.js
    - RegistrationController.js
    - ProductDetailController.js
    - SearchResultsController.js
- directives.js
- filters.js
- models/
    - CartModel.js
    - ProductModel.js
    - SearchResultsModel.js
    - UserModel.js
- services/
    - CartService.js
    - UserService.js
    - ProductService.js

不錯(cuò),現(xiàn)在已經(jīng)可以通過樹形文件目錄或者IDE快捷鍵更方便地查找文件了,源代碼控制中的變更集(changeset)也能夠清楚地描述文件修改記錄。雖然已經(jīng)獲得了極大的改進(jìn),但是仍有一定的局限性。

想象一下,你現(xiàn)在正在辦公室,突然發(fā)現(xiàn)明天有個(gè)商務(wù)出差,需要幾套干洗的衣服,因此給家里打電話告訴另一半把黑色和藍(lán)色的西裝交給清潔工,還有黑紋領(lǐng)帶配灰色襯衫、白襯衫配純黃領(lǐng)帶。如果你的另一半并不熟悉衣柜,又該如何從三條黃色的領(lǐng)帶中挑出你的正確需求?

模塊化

希望衣服的比喻沒有讓你覺得過于陳舊。下面舉一個(gè)實(shí)例,

  • 你的搭檔是新來的開發(fā)者,他被要求去修補(bǔ)這個(gè)復(fù)雜應(yīng)用中的一處bug。
  • 他掃過這些文件夾,看到了controllers、modelsservices等文件夾整齊地排列著,但是他仍然不清楚對(duì)象間的依賴關(guān)系。
  • 處于某些原因,他希望能夠重用部分代碼,這需要從各個(gè)文件夾中搜集相關(guān)文件,而且常常會(huì)遺漏某些文件夾中的對(duì)象。

信或不信,你確實(shí)很少會(huì)在新項(xiàng)目中重用很多代碼,但你很可能需要重用登陸系統(tǒng)這樣的整個(gè)模塊。所以,是不是按功能劃分文件會(huì)更好?

下面的文件結(jié)構(gòu)是以功能劃分后的應(yīng)用結(jié)構(gòu),


- cart/
    - CartModel.js
    - CartService.js
- common/
    - directives.js
    - filters.js
- product/
    - search/
        - SearchResultsController.js
        - SearchResultsModel.js
    - ProductDetailController.js
    - ProductModel.js
    - ProductService.js
- user/
    - LoginController.js
    - RegistrationController.js
    - UserModel.js
    - UserService.js

雖然現(xiàn)實(shí)世界中有空間限制,難以隨意整理服裝,但是編程中類似的處理卻是零成本的。

現(xiàn)在即使是新來的開發(fā)者也能通過頂級(jí)文件夾的命名理解應(yīng)用的功能,相同文件夾下的文件會(huì)存在互相依賴等關(guān)系,而且僅僅通過瀏覽文件組織結(jié)構(gòu)就能輕易理解登錄、注冊(cè)等功能的原理。新的項(xiàng)目也可以通過復(fù)制粘貼來重用其中的代碼了。

使用AngularJS我們可以進(jìn)一步將相關(guān)代碼組織為模塊,


var userModule = angular.module('userModule',[]);
userModule.factory('userService', ['$http', function($http) {
    return new UserService($http);
}]);
userModule.factory('userModel', ['userService', function(userService) {
    return new UserModel(userService);
}]);
userModule.controller('loginController', ['$scope', 'userModel', LoginController]);
userModule.controller('registrationController', ['$scope', 'userModel', RegistrationController]);

如果我們將UserModule.js文件放到user文件夾,它就成了這個(gè)模塊中使用到的對(duì)象的“manifest”,這也是適合RequireJS或者Browserify中放置某些加載指令的地方。

如何處理通用代碼

每個(gè)應(yīng)用都會(huì)有某些代碼廣泛使用在多個(gè)模塊中,我們常常使用名為commom或者shared的文件夾來存放這些功能代碼。

又該如何處理這些通用代碼呢?

  • 如果模塊中的對(duì)象需要直接訪問幾個(gè)通用對(duì)象,為這些對(duì)象提供幾個(gè)Facade(外觀模式)。這有助于減少每個(gè)對(duì)象的依賴者,而過多的關(guān)聯(lián)對(duì)象通常意味著糟糕的代碼結(jié)構(gòu)。
  • 如果通用模塊變得過于龐大,你需要將它按功能領(lǐng)域細(xì)分為多個(gè)子模塊。確保每個(gè)應(yīng)用模塊只使用它需要的通用模塊,這即是SOLID接口隔離原則的變種。
  • 在根范圍($rootScope)添加實(shí)體,這樣子范圍也可以使用,適合多個(gè)控制器都依賴同一個(gè)對(duì)象(比如PermissionsModel)的情況。
  • 在解耦兩個(gè)不明確互相引用的組件時(shí),請(qǐng)使用事件。Angular中Scope對(duì)象的$emit、$broadcast以及$on方法使得這種方式變得現(xiàn)實(shí)??刂破髂軌蛴|發(fā)一個(gè)事件來執(zhí)行某些動(dòng)作,然后再動(dòng)作結(jié)束后收到相應(yīng)地通知。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)