javascript
AngularJS之代码风格36条建议【一】(九)
前言
其實在新學(xué)一門知識時,我們應(yīng)該注意下怎么書寫代碼更加規(guī)范,從開始就注意養(yǎng)成一個良好的習(xí)慣無論是對于bug的查找還是走人后別人熟悉代碼都是非常好的,利人利己的事情何樂而不為呢,關(guān)于AngularJS中的代碼風(fēng)格分為幾節(jié)來闡述。希望對打算學(xué)習(xí)AngularJS的新手或者已經(jīng)在路上的老手有那么一丟丟的幫助也是可以的。
普遍規(guī)則
tips 01(定義一個組件腳本文件時,建議此文件的代碼少于400行)
(1)有利于單元測試和模擬測試。
(2)增加可讀性、可維護性、避免和團隊在源代碼控制上的沖突。
(3)當(dāng)在文件中組合組件時,可能會共享變量、依賴不需要的耦合從而避免潛在的bugs。
避免如下這樣做:
angular.module('app', ['ngRoute']).controller('SomeController', SomeController).factory('someFactory', someFactory);function SomeController() { }function someFactory() { }相同的組件應(yīng)該分為各自的文件(推薦如下做):
// 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的組件應(yīng)該作為匿名函數(shù)立即被調(diào)用)
(1)匿名函數(shù)移除了全局作用域中的變量,能夠避免變量沖突以及變量長期存在于內(nèi)存中。
(2)當(dāng)代碼經(jīng)過捆綁和壓縮到單個文件中,并將其文件部署到生產(chǎn)服務(wù)器中時會產(chǎn)生全局變量的沖突。
避免如下這樣做:
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(使用模塊時避免使用變量代替的應(yīng)該是鏈?zhǔn)秸Z法)
將使代碼更加可讀,避免變量的沖突和泄漏。
避免如下這樣做:
var app = angular.module('app'); app.controller('SomeController', SomeController);function SomeController() { }推薦如下這樣做:
angular.module('app').controller('SomeController', SomeController);function SomeController() { }命名函數(shù)vs匿名函數(shù)
tips 05 (使用命名函數(shù)來作為函數(shù)的回調(diào)而非匿名函數(shù))
使代碼易讀,易于調(diào)試且降低嵌套代碼的回調(diào)量。
避免如下這樣做:
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(使用控制器內(nèi)部使用controllerAs語法代替scope語法即再內(nèi)部用this代替scope語法即再內(nèi)部用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關(guān)鍵字代表上下文,在控制器內(nèi)部使用函數(shù)時可能會改變它的上下文,用一個變量來捕獲this能夠避免面臨這樣的問題。
避免如下:
function CustomerController() {this.name = {};this.sendMessage = function() { }; }推薦如下:
function CustomerController() {var vm = this;vm.name = {};vm.sendMessage = function() { }; }tips 09(在控制器的最頂部按照字母大小來排序,而非通過控制器代碼來進行擴展)
(1)在頂部綁定成員易于閱讀同時幫助我們識別可以在控制器中綁定的成員并在視圖中使用。
(2)使用匿名函數(shù)雖然可能,但是一旦代碼量超過一定數(shù)量則降低了代碼的可閱讀性。
避免如下:
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 (使用聲明式函數(shù)來隱藏實現(xiàn)細節(jié))
使用聲明式函數(shù)來隱藏實現(xiàn)細節(jié),并保持綁定的成員在頂部。當(dāng)在控制器中需要綁定一個函數(shù)時,指向它到一個函數(shù)式聲明緊接著在下面。即將成員綁定在頂部且使用聲明式函數(shù)。
避免如下:
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(在控制器中通過服務(wù)和工廠將業(yè)務(wù)邏輯導(dǎo)入其中)
(1)業(yè)務(wù)邏輯可能在多個控制器中被重用,將服務(wù)通過函數(shù)進行暴露。
(2)在單元測試中,業(yè)務(wù)邏輯更容易被隔離,在控制器中進行調(diào)用時更容易被模擬。
(3)消除了依賴且在控制器中隱藏了實現(xiàn)的細節(jié)。
避免如下:
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 (保持控制器關(guān)注)
對一個視圖定義一個控制器,對于其他控制器不要重用控制器,代替的是將重用邏輯移到工廠以此來保持控制器簡單,更多的是關(guān)注視圖。
tips 13(分配控制器)
當(dāng)控制器必須和一個視圖配對并且組件會被其他控制器和視圖重用時,通過路由來定義控制器。
避免如下:
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>服務(wù)
tips 14(單例)
服務(wù)被初始化通過new關(guān)鍵字,使用this關(guān)鍵字來修飾方法和變量,因為所有的服務(wù)是單例對象,所以對于每個injector的服務(wù)只有唯一的實例。
推薦如下:
// 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)在頂部暴露要調(diào)用的服務(wù)的成員,加強可讀性以及單元測試。
(2)當(dāng)文件足夠大時,可能需要滾動才能看到其暴露的函數(shù)。
(3)通過服務(wù)定義的接口在代碼量超過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() {/* */}; }服務(wù)
tips 16 (重構(gòu)服務(wù))
對于數(shù)據(jù)操作和將數(shù)據(jù)與工廠進行交互時重構(gòu)邏輯,使數(shù)據(jù)服務(wù)負責(zé)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)很容易將所有指令混合在一個文件中,但是很難對于共享跨應(yīng)用程序或者共享模塊等。
(2)每一個文件一個指令利于代碼的可維護性。
避免如下:
推薦如下:
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。
可以用唯一的指令前綴來標(biāo)識指令的背景和來源,例如上述的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)
當(dāng)在指令中使用controllerAs語法時,若我們想綁定外部作用域到指令的控制器的作用域令bindToController = true。
如上述tips 20初始化文本值為vm.max為undifined,若設(shè)置bindToController = true,則vm.max = 77;
解析promise?
tips 22(控制器激活promise)
在一個activate函數(shù)中來啟動控制器的邏輯。
(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需要被解析時,在控制器邏輯執(zhí)行之前通過$routerProvider來解析這些依賴。在控制器激活之前,如果需要依據(jù)條件來取消路由,通過路由解析來進行。
(1)在控制器加載之前之前它可能需要獲取數(shù)據(jù),數(shù)據(jù)可能來源于自定義的工廠或$http的promise。使用路由解析使得promise在控制器邏輯執(zhí)行之前被解析,因此它可能根據(jù)在promise的數(shù)據(jù)來采取不同的動作。
(2)在路由和控制器的激活函數(shù)中的代碼執(zhí)行之后,視圖開始被正確加載,當(dāng)激活promise解析時,數(shù)據(jù)綁定開始進行。
避免如下:
angular.module('app').controller('AvengersController', AvengersController);function AvengersController(movieService) {var vm = this;// unresolved vm.movies;// resolved asynchronouslymovieService.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; }或者推薦如下操作(更易于調(diào)試和處理依賴注入):
// 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鏈中維護異常。
在服務(wù)或者工廠中一定要處理異常。
(1)如果一個catch模塊沒有返回一個reject的promise,那么此時這個promise的調(diào)用者不知道異常的出現(xiàn),接著調(diào)用者的then然后被執(zhí)行,用戶根本不知道發(fā)生了什么。
(2)避免隱藏的錯誤以及誤導(dǎo)用戶。
避免如下:
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);} }手動標(biāo)注依賴注入
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) { }注意:當(dāng)函數(shù)是如下一個返回語句,此時inject可能無法訪問(例如在指令中),此時解決這個問題的辦法是將inject可能無法訪問(例如在指令中),此時解決這個問題的辦法是將inject移動到控制器的外面。
tips 26($inject無效的情況)
避免如下:
function outer() {var ddo = {controller: DashboardPanelController,controllerAs: 'vm'};return ddo;DashboardPanelController.$inject = ['logger']; // Unreachablefunction 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服務(wù),當(dāng)異常出現(xiàn)時在provider服務(wù),當(dāng)異常出現(xiàn)時在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(創(chuàng)建工廠并暴露其接口來捕獲異常)?
在代碼執(zhí)行過程中可能會拋出異常,我們需要提供統(tǒng)一的方式來捕獲異常。
推薦如下:
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和document和window代替document和window)
在AngularJS中存在document和document和window兩個服務(wù)來代替document和window利于模擬和測試。
tips 31(使用interval和interval和timeout代替interval和timeout)
?在AngularJS中存在interval和interval和timeout兩個服務(wù)來代替interval和timeout利于測試和處理Angular的digest生命周期從而保持數(shù)據(jù)同步綁定。
命名
通過使用統(tǒng)一的命名方式來為所有組件命名,推薦的方式為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.jstips 33(控制器命名后綴為Controller)
控制器命名后綴是最常用且更明確、具體的描述。
推薦如下:
angular.module.controller('AvengersController', AvengersController);function AvengersController() { }tips 34(工廠和服務(wù)命名)?
根據(jù)其特征來統(tǒng)一為所有服務(wù)和工廠來命名,使用駱駝風(fēng)格來命名。避免工廠和服務(wù)前綴使用$。
(1)提供一致的方式來快速識別和引用工廠。
(2)避免命名沖突。
(3)清除服務(wù)名稱,如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(指令組件命名)
通過使用駱駝風(fēng)格來為指令組件統(tǒng)一命名,使用短前綴來描述這個區(qū)域信息(例如:前綴可為公司名稱或者項目名稱)。
提供統(tǒng)一的方式來快速識別和引用組件
推薦如下:
// cnblogs-profile.directive.js angular.module.directive('xxCnblogsProfile', xxCnblogsrProfile);// usage is <xx-cnblogs-profile> </xx-cnblogs-profile>function xxCnblogsProfile() { }總結(jié)
本節(jié)我們講了在AngularJS中的代碼風(fēng)格,我們可以一定不需要這樣做,但是我們推薦這樣做。
本文轉(zhuǎn)自Jeffcky博客園博客,原文鏈接:http://www.cnblogs.com/CreateMyself/p/5556836.html,如需轉(zhuǎn)載請自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的AngularJS之代码风格36条建议【一】(九)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PyCharm:ModuleNotFou
- 下一篇: 网络传输数据的加密过程详解