[譯] Angularjs 編程風格引導(一)

一個好的編程風格有助於團隊的協同開發,因此在作 angular 開發時,咱們也有一些約定,本文章主要是針對於使用 angular 和 coffeescript 編程的團隊。(這是一個粗糙的翻譯版本,原文的連接在文章下,感興趣的同窗能夠去看)javascript

Angular 編程風格指導原文地址html

1. 單一職責

原則 1:一個文件只能定義一個組件java

下面的例子定義了app模塊和他的依賴,把控制器和服務都定義到一個文件了git

--不推薦--
SomeController = ()->
someFactory = ()->
angular
    .module('app', ['ngRoute'])
    .controller('SomeController' , SomeController)
    .factory('someFactory' , someFactory)

一樣的組建,如今咱們把它分解到他們本身的文件中github

--推薦方式--
--app.module.js--

angular
    .module('app', ['ngRoute'])
    

--推薦方式--
--someController.js--
SomeController = ()->
angular
    .module('app')
    .controller('SomeController' , SomeController)
--推薦方式--
--someFactory.js--
someFactory = ()->
angular
    .module('app')
    .factory('someFactory' , someFactory)

2. 模塊

原則 2:定義模塊的時候不要用變量來定義,用設置的語法來定義編程

由於咱們使用的單一職責的原則,每個組建一個文件,在定義組建的時候你已經用angular.module來介紹這個組建是哪一個模塊的了。session

-- 不推薦 --
    app = angular.module('app', [
        'ngAnimate'
        'ngRoute'
        'app.shared'
        'app.dashboard'
    ])

下面是不用變量的語法app

-- 推薦方式 --
    angular
        .module('app', [
        'ngAnimate'
        'ngRoute'
        'app.shared'
        'app.dashboard'
    ])

拓展:當使用模塊的時候,不要用變量,要用鏈式定義的語法ide

這樣作可以使代碼的可讀性更高,同時也能避免變量的泄露和碰撞(重名)函數

-- 不推薦--
    app = angular.module('app')
    app.controller('SomeController' , SomeController)
    SomeController = ()->
    
    --推薦方式--
    SomeController = ()->
    
    angular
      .module('app')
      .controller('SomeController' , SomeController)

設置 vs 獲取全部的實例只要設置一次

一個模塊只要被建立一次,以後的模塊獲取只要經過這個切入點來獲取就能夠了

  • angular.module('app', []) 來設置模塊

  • angular.module('app')來獲取模塊

命名函數 vs 匿名函數:使用命名函數來代替向回掉函數傳遞匿名函數

這樣可以是代碼的可讀性更高,更加容易debug,減小回調函數的嵌套

-- 不推薦 --
    angular
      .module('app')
      .controller('Dashboard', ()->)
      .factory('logger', ()-> )
      
    --推薦方式--
    --dashboard.js--
    Dashboard = ()->
      # logic goes here -->
      return
    
    angular
      .module('app')
      .controller('Dashboard', Dashboard)

    --推薦方式--
    -- logger.js --
    logger = ()->
      # logic goes here -->
      return
    
    angular
      .module('app')
      .factory('logger', logger)

IIFE(當即調用函數表達式):把angular 組建包裹在可以立刻調用的函數表達式中

IIFE 把變量從全局做用域裏解放出來,這樣作能防止把變量和函數定義在全局做用句中從而形成變量的碰撞()變量重名形成的莫名其妙的bug)

(->
  logger = ()->
    # logic goes here -->
    return
    
  angular
    .module('app')
    .factory('logger', logger);

)()

主意:爲了使代碼更加簡潔,下面的編程先省略 IIFE 語法

3. 控制器

controller as view 語法:controllerAs語法來代替經典的把controller 綁定到 $scope 做用域的語法

  • 控制器是一個構造類,須要經過newed來建立一個新的實例,可是controllerAS語法更像 javascript 的構造函數

  • 這樣的用法可以促進咱們在視圖中綁定對應對象的變量( 用customer.name來取代name)等等,這樣可以是咱們的代碼更易讀,避免咱們沒有指定對象的時候的一些參考問題。

  • 可以讓咱們避免在視圖中的嵌套控制器中使用$parent來調用父級控制器

    -- 不推薦 --
            <div ng-controller="Customer">
              {{ name }}
            </div>
            -- 推薦方式 --
            <div ng-controller="Customer as customer">
              {{ customer.name }}
            </div>

controllerAs Controller Syntax:用controllerAs來代替傳統的將控制器綁定到$scope的語法

  • controllerAs經過 this來從控制器的內部把返回的內容綁定到$scope 上
    controllerAs在語法上比 $scope要友好,使用controllerAs 你依舊能夠把把數據綁定到視圖,依舊能夠訪問綁定在$scope上的方法

  • 可以避免可能把方法定義到服務上更好的時候,想要將控制器中的方法綁定到$scope上,在服務中要考慮$scope 的用法,在控制器中只有必要的時候才能綁定到$scope上,舉個例子,當要經過$emit,$broadcast,$on來傳遞和接收事件的時候,要在服務中定義,再在控制器中調用
    主意:介於 coffeescript 會自動返回最後一行,咱們最好在函數的最後一行加上一個return 聲明(即便這個函數沒有任何東西返回),大多數上能夠沒有返回聲明,可是在我本身的開發過程當中,沒有返回聲明的時候,會報錯,因此建議仍是加上這個返回聲明

-- 不推薦 --
    (->
      Customer = ($scope)->
        $scope.name = {}
        $scope.sendMessage = ()->
      angular
        .module('app')
        .controller('Customer', Customer)
    )()


    -- 推薦方法 --
    (->
      Customer = ()->
        @name = {}
        @sendMessage = ()->
        return
    
      angular
        .module('app')
        .controller('Customer', Customer)
    )()

controllerAS with vm當使用controllerAs語法時,用一個變量來代替 this ,找一個統一的變量名來代替視圖模型例如 vm

this關鍵字是聯繫上下文的,當在控制器中使用函數的時候可能會改變上下文,爲了不這樣的狀況,最好用一個變量來捕獲this

--不推薦--
    (->
      Customer = ()->
        @name = {}
        @sendMessage = ()->
          # here @/this is not the same
          @stuff = "stuff"
    
        return
      angular
        .module('app')
        .controller('Customer', Customer)
    )()
    
    
    --推薦方式--
    (->
      Customer = ()->
        vm = @
        vm.name = {}
        vm.sendMessage = ()->
        
        return
      angular
        .module('app')
        .controller('Customer', Customer)
    )()

【tip】

### OR use the fat arrow in functions => ###
    (->
      Customer = ()->
        @name = {}
        @sendMessage = ()=>
          @stuff
    
        return
      angular
        .module('app')
        .controller('Customer', Customer)
    )()

【note】:你能夠把下面這段代碼放到你的代碼的最前一行,來避免一些組建的語法檢查

### jshint validthis: true ###
    vm = @

把變量放在控制器的最前面

把數據綁定的變量成員(按照字母順序)放在控制器的最前面,而不是遍及在整個控制器中。

把數據綁定的變量成員放在控制器的最前面,使程序更加易讀,幫你當即分辨控制器中這個變量成員能夠綁定在視圖中

-- 不推薦 --
    function SessionsController() {
        var vm = this;
    
        vm.gotoSession = function() {
          /* ... */
        };
        vm.refresh = function() {
          /* ... */
        };
        vm.search = function() {
          /* ... */
        };
        vm.sessions = [];
        vm.title = 'Sessions';
    }
    
    -- 推薦方式 --
    function SessionsController() {
        var vm = this;
    
        vm.gotoSession = gotoSession;
        vm.refresh = refresh;
        vm.search = search;
        vm.sessions = [];
        vm.title = 'Sessions';
    
        ////////////
    
        function gotoSession() {
          /* */
        }
    
        function refresh() {
          /* */
        }
    
        function search() {
          /* */
        }
    }

【note】 若是函數是一行的就把函數也放在控制器的前面,這樣作對代碼的閱讀性不會有影響

/* 不推薦*/
    function SessionsController(data) {
        var vm = this;
    
        vm.gotoSession = gotoSession;
        vm.refresh = function() {
            blabla
        };
        vm.search = search;
        vm.sessions = [];
        vm.title = 'Sessions';
    }
    
    
    
    /* 推薦方法*/
    function SessionsController(sessionDataService) {
        var vm = this;
    
        vm.gotoSession = gotoSession;
        vm.refresh = sessionDataService.refresh; // 1 liner is OK
        vm.search = search;
        vm.sessions = [];
        vm.title = 'Sessions';
    }

函數聲明,隱藏實現細節

函數聲明,隱藏實現的細節。把用於數據綁定的變量成員的聲明,放在控制器的前面,實現放在文件的後面。

  • 把數據綁定的成員放在前面易於代碼的閱讀,能讓你一眼就分辨出哪一個變量用於視圖的哪塊區域的綁定

  • 把函數的實現放在文件的後面,把函數實現這部分比較複雜的部分放在後面。能讓你直接看到函數聲明這部分重要的信息

  • 由於函數的聲明被提高了,因此在函數定義前,你是不須要關心這個函數的

  • 你不須要擔憂函數聲明的位子移動的問題,你不用擔憂"由於a函數是依賴於b函數的,a函數移動到b函數前面會讓你的代碼出錯"這樣子的問題

  • 順序在函數表達式中很重要

    /*不建議使用函數表達式*/
       function AvengersController(avengersService, logger) {
           var vm = this;
           vm.avengers = [];
           vm.title = 'Avengers';
       
           var activate = function() {
               return getAvengers().then(function() {
                   logger.info('Activated Avengers View');
               });
           }
       
           var getAvengers = function() {
               return avengersService.getAvengers().then(function(data) {
                   vm.avengers = data;
                   return vm.avengers;
               });
           }
       
           vm.getAvengers = getAvengers;
       
           activate();
       }

主意上面的例子中把重要的函數聲明分散在控制器中,但在下面的例子中重要的函數聲明都被提高到了頂部。舉個栗子,函數綁定變量如vm.avengers vm.title,把函數實現的細節放在後面,這樣更加有利於代碼的閱讀

/*  推薦方法 */
    function AvengersController(avengersService, logger) {
        var vm = this;
        vm.avengers = [];
        vm.getAvengers = getAvengers;
        vm.title = 'Avengers';
    
        activate();
    
        function activate() {
            return getAvengers().then(function() {
                logger.info('Activated Avengers View');
            });
        }
    
        function getAvengers() {
            return avengersService.getAvengers().then(function(data) {
                vm.avengers = data;
                return vm.avengers;
            });
        }
    }

把控制器的邏輯定義放到服務中

  • 邏輯可能會在多個控制器中複用,經過封裝在服務中來經過函數暴露給控制器

  • 把邏輯封裝在服務中,能更容易在單元測試中分離成獨立做用域,在控制器中調用函數也更容易模擬

  • 從控制器中移除依賴,把實現細節隱藏起來

  • 讓控制器變得簡單,苗條,且專一

    /* 不推薦 */
       function OrderController($http, $q, config, userInfo) {
           var vm = this;
           vm.checkCredit = checkCredit;
           vm.isCreditOk;
           vm.total = 0;
       
           function checkCredit() {
               var settings = {};
               // Get the credit service base URL from config
               // Set credit service required headers
               // Prepare URL query string or data object with request data
               // Add user-identifying info so service gets the right credit limit for this user.
               // Use JSONP for this browser if it doesn't support CORS
               return $http.get(settings)
                   .then(function(data) {
                    // Unpack JSON data in the response object
                      // to find maxRemainingAmount
                      vm.isCreditOk = vm.total <= maxRemainingAmount
                   })
                   .catch(function(error) {
                      // Interpret error
                      // Cope w/ timeout? retry? try alternate service?
                      // Re-reject with appropriate error for a user to see
                   });
           };
       }
       /* 推薦方法 */
       function OrderController(creditService) {
           var vm = this;
           vm.checkCredit = checkCredit;
           vm.isCreditOk;
           vm.total = 0;
       
           function checkCredit() {
              return creditService.isOrderTotalOk(vm.total)
                 .then(function(isOk) { vm.isCreditOk = isOk; })
                 .catch(showError);
           };
       }

使控制器變得專一

爲一個視圖定義一個控制器,不要講一個控制器爲多個視圖複用,要把複用的邏輯封裝到服務中,保證一個控制器只專一於他的視圖

給多個視圖複用的控制器是很脆的,一個好的 E2E測試覆蓋率是用來肯定一個應用的穩定性的

路由分配

當一個控制器必須和某個視圖綁定但也有可能和其餘視圖或者控制器複用,把控制器的定義和路由條狀一塊兒定義

【note】若是一個視圖不是經過路由而是經過其餘方式加載的,請用ng-controller="Avengers as vm"語法

在路由中分配控制器,容許不一樣的路由去調用不一樣的控制器和視圖,當控制器是在視圖中經過ng-controller 進行聲明,那麼這個視圖就要和這個控制器一直關聯

/* 不推薦 */
    
    // route-config.js
    angular
        .module('app')
        .config(config);
    
    function config($routeProvider) {
        $routeProvider
            .when('/avengers', {
              templateUrl: 'avengers.html'
            });
    }
    
    <!-- avengers.html -->
    <div ng-controller="AvengersController as vm">
    </div>
    
    
    
    /* 推薦方式 */
    
    // route-config.js
    angular
        .module('app')
        .config(config);
    
    function config($routeProvider) {
        $routeProvider
            .when('/avengers', {
                templateUrl: 'avengers.html',
                controller: 'Avengers',
                controllerAs: 'vm'
            });
    }
    <!-- avengers.html -->
    <div>
    </div>
相關文章
相關標籤/搜索