如何組織大型JavaScript應用中的程式碼?

csdn發表於2013-04-28

  本文作者Cliff Meyers是一個前端工程師,熟悉HTML5、JavaScript、J2EE開發,他在開發過程中總結了自己在應對JavaScript應用越來越龐大情況下的檔案結構,深得其他開發者認可。

  地板上堆放的衣服

  首先,我們來看看angular-seed,它是AngularJS應用開發的官方入門專案,其檔案結構是這樣的:

  • css/
  • img/
  • js/
    • app.js
    • controllers.js
    • directives.js
    • filters.js
    • services.js
  • lib/
  • partials/

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

  這種組織方式很凌亂。一旦你的程式碼中存在6、7個甚至更多的控制器或者服務,檔案管理就會變得難以處理:很難找到想要尋找的物件,原始碼控制中的檔案也變更集變得難懂。

  襪子抽屜

  常見的JavaScript檔案結構還有另一種形式,即按原型將檔案分類。我們繼續用整理衣服來比喻:現在我們買了有很多抽屜的衣櫃,打算將襪子放在其中一個抽屜裡,內衣放在另一個抽屜,再把襯衫整齊地疊在第三個抽屜……

  想象一下,我們正在開發一個簡單的電子商務網站,包括登陸流程、產品目錄以及購物車UI。同樣,我們將檔案分為以下幾個原型:models(業務邏輯和狀態)、controllers以及services(HTTP/JSON端點加密),而按照Angular預設那樣非籠統地歸到“service”架構。因此我們的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

  不錯,現在已經可以通過樹形檔案目錄或者IDE快捷鍵更方便地查詢檔案了,原始碼控制中的變更集(changeset)也能夠清楚地描述檔案修改記錄。雖然已經獲得了極大的改進,但是仍有一定的侷限性。

  想象一下,你現在正在辦公室,突然發現明天有個商務出差,需要幾套乾洗的衣服,因此給家裡打電話告訴另一半把黑色和藍色的西裝交給清潔工,還有黑紋領帶配灰色襯衫、白襯衫配純黃領帶。如果你的另一半並不熟悉衣櫃,又該如何從三條黃色的領帶中挑出你的正確需求?

  模組化

  希望衣服的比喻沒有讓你覺得過於陳舊,下面舉一個例項:

  • 你的搭檔是新來的開發者,他被要求去修補這個複雜應用中的一處bug。
  • 他掃過這些資料夾,看到了controllers、models、services等資料夾整齊地排列著,但是他仍然不清楚物件間的依賴關係。
  • 處於某些原因,他希望能夠重用部分程式碼,這需要從各個資料夾中搜集相關檔案,而且常常會遺漏某些資料夾中的物件。

  信或不信,你確實很少會在新專案中重用很多程式碼,但你很可能需要重用登陸系統這樣的整個模組。所以,是不是按功能劃分檔案會更好?下面的檔案結構是以功能劃分後的應用結構:

  • 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

  雖然現實世界中有空間限制,難以隨意整理服裝,但是程式設計中類似的處理卻是零成本的。

  現在即使是新來的開發者也能通過頂級資料夾的命名理解應用的功能,相同資料夾下的檔案會存在互相依賴等關係,而且僅僅通過瀏覽檔案組織結構就能輕易理解登入、註冊等功能的原理。新的專案也可以通過複製貼上來重用其中的程式碼了。

  使用AngularJS我們可以進一步將相關程式碼組織為模組:

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資料夾,它就成了這個模組中使用到的物件的“manifest”,這也是適合RequireJS或者Browserify中放置某些載入指令的地方

  如何處理通用程式碼

  每個應用都會有某些程式碼廣泛使用在多個模組中,我們常常使用名為“commom”或者“shared”的資料夾來存放這些功能程式碼。又該如何處理這些通用程式碼呢?

  1. 如果模組中的物件需要直接訪問幾個“通用”物件,為這些物件提供幾個Facade(外觀模式)。這有助於減少每個物件的依賴者,而過多的關聯物件通常意味著糟糕的程式碼結構。
  2. 如果“通用”模組變得過於龐大,你需要將它按功能領域細分為多個子模組。確保每個應用模組只使用它需要的“通用”模組,這即是SOLID中“介面隔離原則”的變種。
  3. 在根範圍($rootScope)新增實體,這樣子範圍也可以使用,適合多個控制器都依賴同一個物件(比如“PermissionsModel”)的情況。
  4. 在解耦兩個不明確互相引用的元件時,請使用事件。Angular中Scope物件的$emit、$broadcast以及$on方法使得這種方式變得現實。控制器能夠觸發一個事件來執行某些動作,然後再動作結束後收到相應地通知。

  原文連結:CLIFF MEYERS

相關文章