AngularJS之代碼風格36條建議【一】(九)

前言

其實在新學一門知識時,咱們應該注意下怎麼書寫代碼更加規範,從開始就注意養成一個良好的習慣不管是對於bug的查找仍是走人後別人熟悉代碼都是很是好的,利人利己的事情何樂而不爲呢,關於AngularJS中的代碼風格分爲幾節來闡述。但願對打算學習AngularJS的新手或者已經在路上的老手有那麼一丟丟的幫助也是能夠的。html

廣泛規則

tips 01(定義一個組件腳本文件時,建議此文件的代碼少於400行)

(1)有利於單元測試和模擬測試。ajax

(2)增長可讀性、可維護性、避免和團隊在源代碼控制上的衝突。api

(3)當在文件中組合組件時,可能會共享變量、依賴不須要的耦合從而避免潛在的bugs。promise

避免以下這樣作:服務器

angular
    .module('app', ['ngRoute'])
    .controller('SomeController', SomeController)
    .factory('someFactory', someFactory);

function SomeController() { }

function someFactory() { }

相同的組件應該分爲各自的文件(推薦以下作):session

// app.module.js
angular
    .module('app', ['ngRoute']);
    angular
    .module('app')
    .controller('SomeController', SomeController);

function SomeController() { }
angular
    .module('app')
    .factory('someFactory', someFactory);

function someFactory() { }

JavaScript做用域

tips 02 (包含Angular的組件應該做爲匿名函數當即被調用)

(1)匿名函數移除了全局做用域中的變量,可以避免變量衝突以及變量長期存在於內存中。app

(2)當代碼通過捆綁和壓縮到單個文件中,並將其文件部署到生產服務器中時會產生全局變量的衝突。async

避免以下這樣作:ide

angular
    .module('app')
    .factory('logger', logger);

function logger() { }

angular
    .module('app')
    .factory('storage', storage);

function storage() { }

推薦以下作:函數

(function() {
    'use strict';

    angular
        .module('app')
        .factory('logger', logger);

    function logger() { }
})();
(function() {
    'use strict';

    angular
        .module('app')
        .factory('storage', storage);

    function storage() { }
})();

定義模塊

tips 03 (聲明模塊時不要用變量來返回)

一個文件中的組件,不多使用須要引入一個變量的模塊。

避免以下這樣作:

var app = angular.module('app', [
    'ngAnimate',
    'ngRoute',
    'app.shared',
    'app.dashboard'
]);

推薦以下作:

angular
    .module('app', [
        'ngAnimate',
        'ngRoute',
        'app.shared',
        'app.dashboard'
    ]);

使用模塊

tips 04(使用模塊時避免使用變量代替的應該是鏈式語法)

將使代碼更加可讀,避免變量的衝突和泄漏。

避免以下這樣作:

var app = angular.module('app');
app.controller('SomeController', SomeController);

function SomeController() { }

推薦以下這樣作:

angular
    .module('app')
    .controller('SomeController', SomeController);

function SomeController() { }

命名函數vs匿名函數

tips 05 (使用命名函數來做爲函數的回調而非匿名函數)

使代碼易讀,易於調試且下降嵌套代碼的回調量。

避免以下這樣作:

angular
    .module('app')
    .controller('DashboardController', function() { })
    .factory('logger', function() { });

推薦以下這樣作:

angular
    .module('app')
    .controller('DashboardController', DashboardController);

function DashboardController() { }
angular
    .module('app')
    .factory('logger', logger);

function logger() { }

控制器 

tips 06(使用controllerAs語法代替$scope語法)

避免以下:

<div ng-controller="CustomerController">
    {{ name }}
</div>

推薦以下:

<div ng-controller="CustomerController as customer">
    {{ customer.name }}
</div>

tips 07(使用控制器內部使用controllerAs語法代替$scope語法即再內部用this代替$scope)

避免以下:

function CustomerController($scope) {
    $scope.name = {};
    $scope.sendMessage = function() { };
}

推薦以下:

function CustomerController() {
    this.name = {};
    this.sendMessage = function() { };
}

tips 08(使用VM代替controllerAs語法即便用一個變量來捕獲this,如VM,它表明ViewModel。)

this關鍵字表明上下文,在控制器內部使用函數時可能會改變它的上下文,用一個變量來捕獲this可以避免面臨這樣的問題。

避免以下:

function CustomerController() {
    this.name = {};
    this.sendMessage = function() { };
}

推薦以下:

function CustomerController() {
    var vm = this;
    vm.name = {};
    vm.sendMessage = function() { };
}

tips 09(在控制器的最頂部按照字母大小來排序,而非經過控制器代碼來進行擴展)

(1)在頂部綁定成員易於閱讀同時幫助咱們識別能夠在控制器中綁定的成員並在視圖中使用。

(2)使用匿名函數雖然可能,可是一旦代碼量超過必定數量則下降了代碼的可閱讀性。

避免以下:

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() {
      /* */
    }
}

tips 10 (使用聲明式函數來隱藏實現細節)

使用聲明式函數來隱藏實現細節,並保持綁定的成員在頂部。當在控制器中須要綁定一個函數時,指向它到一個函數式聲明緊接着在下面。即將成員綁定在頂部且使用聲明式函數。

避免以下:

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();
}

推薦以下:

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;
        });
    }
}

tips 11(在控制器中經過服務和工廠將業務邏輯導入其中)

(1)業務邏輯可能在多個控制器中被重用,將服務經過函數進行暴露。

(2)在單元測試中,業務邏輯更容易被隔離,在控制器中進行調用時更容易被模擬。

(3)消除了依賴且在控制器中隱藏了實現的細節。

避免以下:

function OrderController($http, $q, config, userInfo) {
    var vm = this;
    vm.checkCredit = checkCredit;
    vm.isCreditOk;
    vm.total = 0;

    function checkCredit() {
        var settings = {};
        return $http.get(settings)
            .then(function(data) {
               vm.isCreditOk = vm.total <= maxRemainingAmount
            })
            .catch(function(error) {
            });
    };
}

推薦以下:

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);
    };
}

tips 12 (保持控制器關注)

對一個視圖定義一個控制器,對於其餘控制器不要重用控制器,代替的是將重用邏輯移到工廠以此來保持控制器簡單,更多的是關注視圖。

tips 13(分配控制器)

當控制器必須和一個視圖配對而且組件會被其餘控制器和視圖重用時,經過路由來定義控制器。

避免以下:

angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
          templateUrl: 'avengers.html'
        });
}


<div ng-controller="AvengersController as vm">
</div>

推薦以下:

angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'Avengers',
            controllerAs: 'vm'
        });
}

<div>
</div>

服務

tips 14(單例)

服務被初始化經過new關鍵字,使用this關鍵字來修飾方法和變量,由於全部的服務是單例對象,因此對於每一個injector的服務只有惟一的實例。

推薦以下:

// service
angular
    .module('app')
    .service('logger', logger);

function logger() {
  this.logError = function(msg) {
    /* */
  };
}
// factory
angular
    .module('app')
    .factory('logger', logger);

function logger() {
    return {
        logError: function(msg) {
          /* */
        }
   };
}

工廠

tips 15(將訪問成員置頂)

(1)在頂部暴露要調用的服務的成員,增強可讀性以及單元測試。

(2)當文件足夠大時,可能須要滾動才能看到其暴露的函數。

(3)經過服務定義的接口在代碼量超過100行時避免下降代碼的可閱讀性和形成更多的滾動。

避免以下:

function dataService() {
  var someValue = '';
  function save() {
    /* */
  };
  function validate() {
    /* */
  };

  return {
      save: save,
      someValue: someValue,
      validate: validate
  };
}

推薦以下:

function dataService() {
    var someValue = '';
    var service = {
        save: save,
        someValue: someValue,
        validate: validate
    };
    return service;

    ////////////

    function save() {
        /* */
    };

    function validate() {
        /* */
    };
}

服務

tips 16 (重構服務)

對於數據操做和將數據與工廠進行交互時重構邏輯,使數據服務負責ajax等或其餘操做。

推薦以下:

angular
    .module('app.core')
    .factory('dataservice', dataservice);

dataservice.$inject = ['$http', 'logger'];

function dataservice($http, logger) {
    return {
        getAvengers: getAvengers
    };

    function getAvengers() {
        return $http.get('/api/maa')
            .then(getAvengersComplete)
            .catch(getAvengersFailed);

        function getAvengersComplete(response) {
            return response.data.results;
        }

        function getAvengersFailed(error) {
            logger.error('XHR Failed for getAvengers.' + error.data);
        }
    }
}

指令

tips 17(爲每一個指令定義一個文件,並以此指令命名)

(1)很容易將全部指令混合在一個文件中,可是很難對於共享跨應用程序或者共享模塊等。

(2)每個文件一個指令利於代碼的可維護性。

避免以下:

angular
    .module('app.widgets')

    .directive('orderCalendarRange', orderCalendarRange)

    .directive('salesCustomerInfo', salesCustomerInfo)

function orderCalendarRange() {

}

function salesCustomerInfo() {
}

推薦以下:

angular
    .module('sales.order')
    .directive('acmeOrderCalendarRange', orderCalendarRange);

function orderCalendarRange() {
}
angular
    .module('sales.widgets')
    .directive('acmeSalesCustomerInfo', salesCustomerInfo);

function salesCustomerInfo() {
}

tips 18 (提供惟一的指令前綴)

提供一個短的、惟一的、描述性的指令前綴。例如cnblogsIngUserInfo,則在html中被聲明爲cnblogs-ing-user-info。

能夠用惟一的指令前綴來標識指令的背景和來源,例如上述的cnblogsIngUserInfo,cnblogs表明博客園,而Ing表明閃存,User表明用戶,info表明信息。

tips 19(對元素和特性進行約束) 

在AngularJS 1.3+默認的是EA,在此之下須要用Restrict來進行限制。

避免以下:

<div class="my-calendar-range"></div>

推薦以下:

<my-calendar-range></my-calendar-range>
<div my-calendar-range></div>

tips 20 (在指令中使用controllerAs語法與控制器和視圖中使用該語法要一致)

推薦以下:

<div my-example max="77"></div>
angular
    .module('app')
    .directive('myExample', myExample);

function myExample() {
    var directive = {
        restrict: 'EA',
        templateUrl: 'app/feature/example.directive.html',
        scope: {
            max: '='
        },
        controller: ExampleController,
        controllerAs: 'vm'
    };

    return directive;
}

function ExampleController() {
    var vm = this;
    vm.min = 3;
    console.log('CTRL: vm.min = %s', vm.min);
    console.log('CTRL: vm.max = %s', vm.max);
}
<!-- example.directive.html -->
<div>hello world</div>
<div>max={{vm.max}}<input ng-model="vm.max"/></div>
<div>min={{vm.min}}<input ng-model="vm.min"/></div>

tips 21(在指令添加屬性bindToController = true)

當在指令中使用controllerAs語法時,若咱們想綁定外部做用域到指令的控制器的做用域令bindToController = true。

如上述tips 20初始化文本值爲vm.max爲undifined,若設置bindToController = true,則vm.max = 77;

解析promise 

tips 22(控制器激活promise)

在一個activate函數中來啓動控制器的邏輯。

(1)在一致的地方放置啓動邏輯有利於問題的定位以及測試,同時避免在跨控制器中傳播激活邏輯。

(2)控制器激活能夠更方便地重用刷新的控制器或者視圖邏輯,保持邏輯在一塊兒,使得更快加載視圖。

避免以下:

function AvengersController(dataservice) {
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    dataservice.getAvengers().then(function(data) {
        vm.avengers = data;
        return vm.avengers;
    });
}

推薦以下:

function AvengersController(dataservice) {
    var vm = this;
    vm.avengers = [];
    vm.title = 'Avengers';

    activate();

    ////////////

    function activate() {
        return dataservice.getAvengers().then(function(data) {
            vm.avengers = data;
            return vm.avengers;
        })
    }
}

tips 23(路由解析promise) 

在控制器被激活以前,若控制器依賴於promise須要被解析時,在控制器邏輯執行以前經過$routerProvider來解析這些依賴。在控制器激活以前,若是須要依據條件來取消路由,經過路由解析來進行。

(1)在控制器加載以前以前它可能須要獲取數據,數據可能來源於自定義的工廠或$http的promise。使用路由解析使得promise在控制器邏輯執行以前被解析,所以它可能根據在promise的數據來採起不一樣的動做。

(2)在路由和控制器的激活函數中的代碼執行以後,視圖開始被正確加載,當激活promise解析時,數據綁定開始進行。

避免以下:

angular
    .module('app')
    .controller('AvengersController', AvengersController);

function AvengersController(movieService) {
    var vm = this;
    // unresolved
    vm.movies;
    // resolved asynchronously
    movieService.getMovies().then(function(response) {
        vm.movies = response.movies;
    });
}

推薦以下:

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'AvengersController',
            controllerAs: 'vm',
            resolve: {
                moviesPrepService: function(movieService) {
                    return movieService.getMovies();
                }
            }
        });
}

// avengers.js
angular
    .module('app')
    .controller('AvengersController', AvengersController);

AvengersController.$inject = ['moviesPrepService'];
function AvengersController(moviesPrepService) {
    var vm = this;
    vm.movies = moviesPrepService.movies;
}

或者推薦以下操做(更易於調試和處理依賴注入):

// route-config.js
angular
    .module('app')
    .config(config);

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'AvengersController',
            controllerAs: 'vm',
            resolve: {
                moviesPrepService: moviesPrepService
            }
        });
}

function moviesPrepService(movieService) {
    return movieService.getMovies();
}

// avengers.js
angular
    .module('app')
    .controller('AvengersController', AvengersController);

AvengersController.$inject = ['moviesPrepService'];
function AvengersController(moviesPrepService) {
      var vm = this;
      vm.movies = moviesPrepService.movies;
}

tips 24(用promise來處理異常) 

一個promise的catch模塊必需要返回一個reject的promise來在promise鏈中維護異常。

在服務或者工廠中必定要處理異常。

(1)若是一個catch模塊沒有返回一個reject的promise,那麼此時這個promise的調用者不知道異常的出現,接着調用者的then而後被執行,用戶根本不知道發生了什麼。

(2)避免隱藏的錯誤以及誤導用戶。

避免以下:

function getCustomer(id) {
    return $http.get('/api/customer/' + id)
        .then(getCustomerComplete)
        .catch(getCustomerFailed);

    function getCustomerComplete(data, status, headers, config) {
        return data.data;
    }

    function getCustomerFailed(e) {
        var newMessage = 'XHR Failed for getCustomer'
        if (e.data && e.data.description) {
          newMessage = newMessage + '\n' + e.data.description;
        }
        e.data.description = newMessage;
        logger.error(newMessage);
        // ***
        // Notice there is no return of the rejected promise
        // ***
    }
}

推薦以下:

function getCustomer(id) {
    return $http.get('/api/customer/' + id)
        .then(getCustomerComplete)
        .catch(getCustomerFailed);

    function getCustomerComplete(data, status, headers, config) {
        return data.data;
    }

    function getCustomerFailed(e) {
        var newMessage = 'XHR Failed for getCustomer'
        if (e.data && e.data.description) {
          newMessage = newMessage + '\n' + e.data.description;
        }
        e.data.description = newMessage;
        logger.error(newMessage);
        return $q.reject(e);
    }
}

手動標註依賴注入

tips 25(手動識別依賴)

使用$inject來識別AngularJS組件中的依賴。

避免以下:

angular
    .module('app')
    .controller('DashboardController',
        ['$location', '$routeParams', 'common', 'dataservice',
            function Dashboard($location, $routeParams, common, dataservice) {}
        ]);

避免以下:

angular
  .module('app')
  .controller('DashboardController',
      ['$location', '$routeParams', 'common', 'dataservice', Dashboard]);

function Dashboard($location, $routeParams, common, dataservice) {
}

推薦以下:

angular
    .module('app')
    .controller('DashboardController', DashboardController);

DashboardController.$inject = ['$location', '$routeParams', 'common', 'dataservice'];

function DashboardController($location, $routeParams, common, dataservice) {
}

注意:當函數是以下一個返回語句,此時$inject可能沒法訪問(例如在指令中),此時解決這個問題的辦法是將$inject移動到控制器的外面。

tips 26($inject無效的狀況)

避免以下:

function outer() {
    var ddo = {
        controller: DashboardPanelController,
        controllerAs: 'vm'
    };
    return ddo;

    DashboardPanelController.$inject = ['logger']; // Unreachable
    function DashboardPanelController(logger) {
    }
}

推薦以下:

function outer() {
    var ddo = {
        controller: DashboardPanelController,
        controllerAs: 'vm'
    };
    return ddo;
}

DashboardPanelController.$inject = ['logger'];
function DashboardPanelController(logger) {
}

tips 27(手動解析路由依賴)

使用$inject來手動識別Angular組件的路由解析依賴。

推薦以下:

function config($routeProvider) {
    $routeProvider
        .when('/avengers', {
            templateUrl: 'avengers.html',
            controller: 'AvengersController',
            controllerAs: 'vm',
            resolve: {
                moviesPrepService: moviesPrepService
            }
        });
}

moviesPrepService.$inject = ['movieService'];
function moviesPrepService(movieService) {
    return movieService.getMovies();
}

異常處理 

tips 28(用decorators來配置處理異常)

配置時使用$provider服務,當異常出現時在$exceptionHandler中使用decorator來處理異常。

提供一致的方式在運行時來處理未捕獲的異常。

推薦以下:

angular
    .module('blocks.exception')
    .config(exceptionConfig);

exceptionConfig.$inject = ['$provide'];

function exceptionConfig($provide) {
    $provide.decorator('$exceptionHandler', extendExceptionHandler);
}

extendExceptionHandler.$inject = ['$delegate', 'toastr'];

function extendExceptionHandler($delegate, toastr) {
    return function(exception, cause) {
        $delegate(exception, cause);
        var errorData = {
            exception: exception,
            cause: cause
        };
        toastr.error(exception.msg, errorData);
    };
}

tips 29(建立工廠並暴露其接口來捕獲異常) 

在代碼執行過程當中可能會拋出異常,咱們須要提供統一的方式來捕獲異常。

推薦以下:

angular
    .module('blocks.exception')
    .factory('exception', exception);

exception.$inject = ['logger'];

function exception(logger) {
    var service = {
        catcher: catcher
    };
    return service;

    function catcher(message) {
        return function(reason) {
            logger.error(message, reason);
        };
    }
}

tips  30(使用$document和$window代替document和window)

在AngularJS中存在$document和$window兩個服務來代替document和window利於模擬和測試。

tips 31(使用$interval和$timeout代替interval和timeout)

 在AngularJS中存在$interval和$timeout兩個服務來代替interval和timeout利於測試和處理Angular的digest生命週期從而保持數據同步綁定。

命名

經過使用統一的命名方式來爲全部組件命名,推薦的方式爲feature.type.js。以下:

文件名:cnblogs.controller.js。

註冊的組件名:CnblogsController。

tips 32(文件命名的特色)

避免以下:

// Controllers
avengers.js
avengers.controller.js
avengersController.js

// Services/Factories
logger.js
logger.service.js
loggerService.js

推薦以下:

// controllers
avengers.controller.js
avengers.controller.spec.js

// services/factories
logger.service.js
logger.service.spec.js

// constants
constants.js

// module definition
avengers.module.js

// routes
avengers.routes.js
avengers.routes.spec.js

// configuration
avengers.config.js

// directives
avenger-profile.directive.js
avenger-profile.directive.spec.js

tips 33(控制器命名後綴爲Controller)

控制器命名後綴是最經常使用且更明確、具體的描述。

推薦以下:

angular
    .module
    .controller('AvengersController', AvengersController);

function AvengersController() { }

tips 34(工廠和服務命名) 

根據其特徵來統一爲全部服務和工廠來命名,使用駱駝風格來命名。避免工廠和服務前綴使用$。

(1)提供一致的方式來快速識別和引用工廠。

(2)避免命名衝突。

(3)清除服務名稱,如logger,不須要其後綴。

推薦以下:

// logger.service.js
angular
    .module
    .factory('logger', logger);

function logger() { }
// credit.service.js
angular
    .module
    .factory('creditService', creditService);

function creditService() { }

// customer.service.js
angular
    .module
    .service('customerService', customerService);

function customerService() { }

tips 36(指令組件命名)

經過使用駱駝風格來爲指令組件統一命名,使用短前綴來描述這個區域信息(例如:前綴可爲公司名稱或者項目名稱)。

提供統一的方式來快速識別和引用組件

推薦以下:

// cnblogs-profile.directive.js
angular
    .module
    .directive('xxCnblogsProfile', xxCnblogsrProfile);

// usage is <xx-cnblogs-profile> </xx-cnblogs-profile>

function xxCnblogsProfile() { }

總結

本節咱們講了在AngularJS中的代碼風格,咱們能夠必定不須要這樣作,可是咱們推薦這樣作。

相關文章
相關標籤/搜索