AngularJs規范


簡介

本風格指南的目的是展示AngularJS應用的最佳實踐和風格指南。 這些最佳實踐來自於:

  1. AngularJS項目源碼
  2. 本人閱讀過的源碼和文章
  3. 本人的實踐經歷

說明1: 這只是風格指南的草案,主要目的是通過交流以消除分歧,進而被社區廣泛采納。
說明2: 本版本是翻譯自英文原版,在遵循下面的指南之前請確認你看到的是比較新的版本。

在本指南中不會包含基本的JavaScript開發指南。這些基本的指南可以在下面的列表中找到:

  1. Google's JavaScript style guide
  2. Mozilla's JavaScript style guide
  3. GitHub's JavaScript style guide
  4. Douglas Crockford's JavaScript style guide
  5. Airbnb JavaScript style guide

對於AngularJS開發,推薦 Google's JavaScript style guide.

在AngularJS的Github wiki中有一個相似的章節 ProLoser, 你可以點擊這里查看。

內容目錄

概覽

目錄結構

由於一個大型的AngularJS應用有較多組成部分,所以最好通過分層的目錄結構來組織。 有兩個主流的組織方式:

  • 按照類型優先,業務功能其次的組織方式

這種方式的目錄結構看起來如下:

.
├── app
│   ├── app.js
│   ├── controllers
│   │   ├── home
│   │   │   ├── FirstCtrl.js
│   │   │   └── SecondCtrl.js
│   │   └── about
│   │       └── ThirdCtrl.js
│   ├── directives
│   │   ├── home
│   │   │   └── directive1.js
│   │   └── about
│   │       ├── directive2.js
│   │       └── directive3.js
│   ├── filters
│   │   ├── home
│   │   └── about
│   └── services
│       ├── CommonService.js
│       ├── cache
│       │   ├── Cache1.js
│       │   └── Cache2.js
│       └── models
│           ├── Model1.js
│           └── Model2.js
├── partials
├── lib
└── test
  • 按照業務功能優先,類型其次的組織方式

如下:

.
├── app
│   ├── app.js
│   ├── common
│   │   ├── controllers
│   │   ├── directives
│   │   ├── filters
│   │   └── services
│   ├── home
│   │   ├── controllers
│   │   │   ├── FirstCtrl.js
│   │   │   └── SecondCtrl.js
│   │   ├── directives
│   │   │   └── directive1.js
│   │   ├── filters
│   │   │   ├── filter1.js
│   │   │   └── filter2.js
│   │   └── services
│   │       ├── service1.js
│   │       └── service2.js
│   └── about
│       ├── controllers
│       │   └── ThirdCtrl.js
│       ├── directives
│       │   ├── directive2.js
│       │   └── directive3.js
│       ├── filters
│       │   └── filter3.js
│       └── services
│           └── service3.js
├── partials
├── lib
└── test
  • 當目錄里有多個單詞時, 使用 lisp-case 語法:
app
 ├── app.js
 └── my-complex-module
     ├── controllers
     ├── directives
     ├── filters
     └── services
  • 在創建指令時,合適的做法是將相關的文件放到同一目錄下 (如:模板文件, CSS/SASS 文件, JavaScript文件)。如果你在整個項目周期都選擇這種組織方式,
app
└── directives
    ├── directive1
    │   ├── directive1.html
    │   ├── directive1.js
    │   └── directive1.sass
    └── directive2
        ├── directive2.html
        ├── directive2.js
        └── directive2.sass

那么,上述的兩種目錄結構均能適用。

  • 組件的單元測試應與組件放置在同一目錄下下。在這種方式下,當改變組件時,更加容易找到對應的測試。同時,單元測試也充當了文檔和示例。
services
├── cache
│   ├── cache1.js
│   └── cache1.spec.js
└── models
    ├── model1.js
    └── model1.spec.js
  • app.js文件包含路由定義、配置和啟動說明(如果需要的話)。
  • 每一個 JavaScript 文件應該僅包含 一個組件 。文件名應該以組件名命名。
  • 使用 Angular 項目模板,如 Yeomanng-boilerplate.

組件命名的約定可以在每個組件中看到。

標記

太長慎讀 把script標簽放在文檔底部。

<!DOCTYPE html>
<html lang="en"> <head> <meta charset="utf-8"> <title>MyApp</title> </head> <body> <div ng-app="myApp"> <div ng-view></div> </div>  <script src="angular.js"></script>  <script src="app.js"></script> </body> </html>

保持標簽的簡潔並把AngularJS的標簽放在標准HTML屬性后面。這樣提高了代碼可讀性。標准HTML屬性和AngularJS的屬性沒有混到一起,提高了代碼的可維護性。

<form class="frm" ng-submit="login.authenticate()"> <div> <input class="ipt" type="text" placeholder="name" require ng-model="user.name"> </div> </form>

其它的HTML標簽應該遵循下面的指南的 建議

標記

下表展示了各個Angular元素的命名約定

元素 命名風格 實例 用途
Modules lowerCamelCase angularApp  
Controllers Functionality + 'Ctrl' AdminCtrl  
Directives lowerCamelCase userInfo  
Filters lowerCamelCase userFilter  
Services UpperCamelCase User constructor
Services lowerCamelCase dataFactory others

其他

  • 使用:
    • $timeout替代 setTimeout
    • $interval instead of setInterval
    • $window替代 window
    • $document 替代 document
    • $http替代 $.ajax

這將使你更易於在測試時處理代碼異常 (例如:你在 setTimeout 中忘記 $scope.$apply)

使用如下工具自動化你的工作流 * Yeoman * Gulp * Grunt * Bower

  • 使用 promise ($q) 而非回調。這將使你的代碼更加優雅、直觀,並且免於回調地獄。
  • 盡可能使用 $resource 而非 $http。更高的抽象可以避免冗余。
  • 使用AngularJS的預壓縮版 (像 ngmin 或 ng-annotate) 避免在壓縮之后出現問題。
  • 不要使用全局變量或函數。通過依賴注入解決所有依賴,這可以減少 bug ,規避很多測試時的麻煩。
  • 為避免使用全局變量或函數,可以借助 Grunt 或 Gulp 把你的代碼放到一個立即執行的函數表達式(IIFE)中。可用的插件有 grunt-wrap 或 gulp-wrap。下面是 Gulp 的示例:
gulp.src("./src/*.js") .pipe(wrap('(function(){\n"use strict";\n<%= contents %>\n})();')) .pipe(gulp.dest("./dist"));
  • 不要污染 $scope。僅添加與視圖相關的函數和變量。
  • 使用 controllers 而非 ngInitngInit 只有在一種情況下的使用是合適的:用來給 ngRepeat的特殊屬性賦予一個別名。除此之外, 你應該使用 controllers 而不是 ngInit 來初始化scope變量。ngInit 中的表達式會傳遞給 Angular 的$parse 服務,通過詞法分析,語法分析,求值等過程。這會導致:
    • 對性能的巨大影響,因為解釋器由 Javascript 寫成
    • 多數情況下,$parse 服務中對表達式的緩存基本不起作用,因為 ngInit 表達式經常只有一次求值
    • 很容易出錯,因為是模板中寫字符串,沒有針對表達式的語法高亮和進一步的編輯器支持
    • 不會拋出運行時錯誤
  • 不要使用 $ 前綴來命名變量, 屬性和方法. 這種前綴是預留給 AngularJS 來使用的.
  • 當使用 DI 機制來解決依賴關系, 要根據他們的類型進行排序 - AngularJS 內建的依賴要優先, 之后才是你自定義的:
module.factory('Service', function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) { return { //Something }; });

模塊

  • 模塊應該用駝峰式命名。為表明模塊 b 是模塊 a 的子模塊, 可以用點號連接: a.b 。

    有兩種常見的組織模塊的方式:

    1. 按照功能組織
    2. 按照組件類型組織

    當前並無太大差別,但前者更加清晰。同時,如果 lazy-loading modules 被實現的話 (當前並未列入 AngularJS 的路線圖),這種方式將改善應用的性能。

控制器

  • 不要在控制器里操作 DOM,這會讓你的控制器難以測試,而且違背了關注點分離原則。應該通過指令操作 DOM。
  • 通過控制器完成的功能命名控制器 (如:購物卡,主頁,控制板),並以字符串Ctrl結尾。
  • 控制器是純 Javascript 構造函數,所以應該用首字母大寫的駝峰命名法(HomePageCtrlShoppingCartCtrl,AdminPanelCtrl, 等等)。
  • 控制器不應該在全局中定義 (盡管 AngularJS 允許,但污染全局命名空間是個糟糕的實踐)。
  • 使用以下語法定義控制器:

    function MyCtrl(dependency1, dependency2, ..., dependencyn) { // ... } module.controller('MyCtrl', MyCtrl);

    為了避免在壓縮代碼時產生問題,你可以使用工具自動生成標准的數組定義式語法,如:ng-annotate (還有 grunt 任務grunt-ng-annotate

  • 使用 controller as 語法:

    <div ng-controller="MainCtrl as main">
       {{ main.title }}
    </div>
    
    app.controller('MainCtrl', MainCtrl); function MainCtrl () { this.title = 'Some title'; }

    使用 controller as 主要的優點是:

    • 創建了一個“獨立”的組件——綁定的屬性不屬於 $scope 原型鏈。這是一個很好的實踐,因為 $scope 原型繼承有一些重要的缺點(這可能是為什么它在 Angular 2 中被移除了):
      • Scope值的改變會在你不注意的地方有影響。
      • 難以重構。
      • dot rule'.
    • 當你不需要做必須由 $scope 完成的操作(比如$scope.$broadcast)時,移除掉了 $scope,就是為 Angular2 做好准備。
    • 語法上更接近於普通的 JavaScript 構造函數。

    想深入了解 controller as ,請看: digging-into-angulars-controller-as-syntax

  • 如果使用數組定義語法聲明控制器,使用控制器依賴的原名。這將提高代碼的可讀性:

    function MyCtrl(s) { // ... } module.controller('MyCtrl', ['$scope', MyCtrl]);

    下面的代碼更易理解

    function MyCtrl($scope) { // ... } module.controller('MyCtrl', ['$scope', MyCtrl]);

    對於包含大量代碼的需要上下滾動的文件尤其適用。這可能使你忘記某一變量是對應哪一個依賴。

  • 盡可能的精簡控制器。將通用函數抽象為獨立的服務。

  • 不要再控制器中寫業務邏輯。把業務邏輯交給模型層的服務。 舉個例子:

    // 這是把業務邏輯放在控制器的常見做法
    angular.module('Store', []) .controller('OrderCtrl', function ($scope) { $scope.items = []; $scope.addToOrder = function (item) { $scope.items.push(item);//-->控制器中的業務邏輯 }; $scope.removeFromOrder = function (item) { $scope.items.splice($scope.items.indexOf(item), 1);//-->控制器中的業務邏輯 }; $scope.totalPrice = function () { return $scope.items.reduce(function (memo, item) { return memo + (item.qty * item.price);//-->控制器中的業務邏輯 }, 0); }; });

    當你把業務邏輯交給模型層的服務,控制器看起來就會想這樣:(關於 service-model 的實現,參看 'use services as your Model'):

    // Order 在此作為一個 'model'
    angular.module('Store', []) .controller('OrderCtrl', function (Order) { $scope.items = Order.items; $scope.addToOrder = function (item) { Order.addToOrder(item); }; $scope.removeFromOrder = function (item) { Order.removeFromOrder(item); }; $scope.totalPrice = function () { return Order.total(); }; });

    為什么控制器不應該包含業務邏輯和應用狀態?

    • 控制器會在每個視圖中被實例化,在視圖被銷毀時也要同時銷毀
    • 控制器是不可重用的——它與視圖有耦合
    • Controllers are not meant to be injected
  • 需要進行跨控制器通訊時,通過方法引用(通常是子控制器到父控制器的通訊)或者 $emit$broadcast 及 $on 方法。發送或廣播的消息應該限定在最小的作用域。

  • 制定一個通過 $emit$broadcast 發送的消息列表並且仔細的管理以防命名沖突和bug。

    Example:

    // app.js
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Custom events:  - 'authorization-message' - description of the message  - { user, role, action } - data format  - user - a string, which contains the username  - role - an ID of the role the user has  - action - specific ation the user tries to perform * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  • 在需要格式化數據時將格式化邏輯封裝成 過濾器 並將其聲明為依賴:

    function myFormat() { return function () { // ... }; } module.filter('myFormat', myFormat); function MyCtrl($scope, myFormatFilter) { // ... } module.controller('MyCtrl', MyCtrl);
  • 有內嵌的控制器時使用 "內嵌作用域" ( controllerAs 語法):

    app.js

    module.config(function ($routeProvider) { $routeProvider .when('/route', { templateUrl: 'partials/template.html', controller: 'HomeCtrl', controllerAs: 'home' }); });

    HomeCtrl

    function HomeCtrl() { this.bindingValue = 42; }

    template.html

    <div ng-bind="home.bindingValue"></div>
    

指令

  • 使用小寫字母開頭的駝峰法命名指令。
  • 在 link function 中使用 scope 而非 $scope。在 compile 中, 你已經定義參數的 post/pre link functions 將在函數被執行時傳遞, 你無法通過依賴注入改變他們。這種方式同樣應用在 AngularJS 項目中。
  • 為你的指令添加自定義前綴以免與第三方指令沖突。
  • 不要使用 ng 或 ui 前綴,因為這些備用於 AngularJS 和 AngularJS UI。
  • DOM 操作只通過指令完成。
  • 為你開發的可復用組件創建獨立作用域。
  • 以屬性和元素形式使用指令,而不是注釋和 class。這會使你的代碼可讀性更高。
  • 使用 scope.$on('$destroy', fn) 來清除。這點在使用第三方指令的時候特別有用。
  • 處理不可信的數據時,不要忘記使用 $sce 。

過濾器

  • 使用小寫字母開頭的駝峰法命名過濾器。
  • 盡可能使過濾器精簡。過濾器在 $digest loop 中被頻繁調用,過於復雜的過濾器將使得整個應用緩慢。
  • 在過濾器中只做一件事。更加復雜的操作可以用 pipe 串聯多個過濾器來實現。

服務

這個部分包含了 AngularJS 服務組件的相關信息。下面提到的東西與定義服務的具體方式(.provider.factory,.service 等)無關,除非有特別提到。

  • 用駝峰法命名服務。

    • 用首字母大寫的駝峰法命名你自己的服務, 把服務寫成構造函數的形式,例如:

      function MainCtrl($scope, User) { $scope.user = new User('foo', 42); } module.controller('MainCtrl', MainCtrl); function User(name, age) { this.name = name; this.age = age; } module.factory('User', function () { return User; });
    • 用首字母小寫的駝峰法命名其它所有的服務。

  • 把業務邏輯封裝到服務中,把業務邏輯抽象為服務作為你的 model。例如:

    //Order is the 'model'
    angular.module('Store') .factory('Order', function () { var add = function (item) { this.items.push (item); }; var remove = function (item) { if (this.items.indexOf(item) > -1) { this.items.splice(this.items.indexOf(item), 1); } }; var total = function () { return this.items.reduce(function (memo, item) { return memo + (item.qty * item.price); }, 0); }; return { items: [], addToOrder: add, removeFromOrder: remove, totalPrice: total }; });

    如果需要例子展現如何在控制器中使用服務,請參考 'Avoid writing business logic inside controllers'。

  • 將業務邏輯封裝成 service 而非 factory,這樣我們可以更容易在服務間實現“經典式”繼承:

    function Human() { //body } Human.prototype.talk = function () { return "I'm talking"; }; function Developer() { //body } Developer.prototype = Object.create(Human.prototype); Developer.prototype.code = function () { return "I'm coding"; }; myModule.service('human', Human); myModule.service('developer', Developer); 
  • 使用 $cacheFactory 進行會話級別的緩存,緩存網絡請求或復雜運算的結果。

  • 如果給定的服務需要配置,把配置相關代碼放在 config 回調里,就像這樣:

    angular.module('demo', []) .config(function ($provide) { $provide.provider('sample', function () { var foo = 42; return { setFoo: function (f) { foo = f; }, $get: function () { return { foo: foo }; } }; }); }); var demo = angular.module('demo'); demo.config(function (sampleProvider) { sampleProvider.setFoo(41); });

模板

  • 使用 ng-bind 或者 ng-cloak 而非簡單的 {{ }} 以防止頁面渲染時的閃爍。
  • 避免在模板中使用復雜的表達式。
  • 當需要動態設置  的 src 時使用 ng-src 而非 src 中嵌套 {{}} 的模板。
  • 當需要動態設置的 href 時使用 ng-href 而非 href 中嵌套 {{ }} 的模板。
  • 通過 ng-style 指令配合對象式參數和 scope 變量來動態設置元素樣式,而不是將 scope 變量作為字符串通過 {{ }} 用於 style 屬性。
<script> ... $scope.divStyle = {  width: 200,  position: 'relative' }; ... </script> <div ng-style="divStyle">my beautifully styled div which will work in IE</div>;

路由

  • 在視圖展示之前通過 resolve 解決依賴。
  • 不要在 resolve 回調函數中顯式使用RESTful調用。將所有請求放在合適的服務中。這樣你就可以使用緩存和遵循關注點分離原則。

國際化

  • 在較新版本的 Angular(>=1.4.0)下,使用內置的 i18n 工具,在較老版本下(<1.4.0),使用 angular-translate

性能

    • 優化 digest cycle

      • 只監聽必要的變量。僅在必要時顯式調用 $digest 循環(例如:在進行實時通訊時,不要在每次接收到消息時觸發$digest 循環)。
      • 對於那些只初始化一次並不再改變的內容, 使用一次性 watcher bindonce (對於早期的 AngularJS)。如果是 AngularJS >=1.3.0 的版本,應使用Angular內置的一次性數據綁定(One-time bindings).
      • 盡可能使 $watch 中的運算簡單。在單個 $watch 中進行繁雜的運算將使得整個應用變慢(由於JavaScript的單線程特性,$digest loop 只能在單一線程進行)
      • 當監聽集合時, 如果不是必要的話不要深度監聽. 最好使用 $watchCollection, 對監聽的表達式和之前表達式的值進行淺層的檢測.
      • 當沒有變量被 $timeout 回調函數所影響時,在 $timeout 設置第三個參數為 false 來跳過 $digest 循環.
      • 當面對超大不太改變的集合, 使用 immutable data structures.
    • 用打包、緩存html模板文件到你的主js文件中,減少網絡請求, 可以用 grunt-html2js / gulp-html2js. 詳見 這里 和 這里 。 在項目有很多小html模板並可以放進主js文件中時(通過minify和gzip壓縮),這個辦法是很有用的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM