标签:
本风格指南的目的是展示AngularJS应用的最佳实践和风格指南。 这些最佳实践来自于:
说明1: 这只是风格指南的草案,主要目的是通过交流以消除分歧,进而被社区广泛采纳。
说明2: 本版本是翻译自英文原版,在遵循下面的指南之前请确认你看到的是比较新的版本。
在本指南中不会包含基本的JavaScript开发指南。这些基本的指南可以在下面的列表中找到:
对于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
app
├── app.js
└── my-complex-module
├── controllers
├── directives
├── filters
└── services
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
文件包含路由定义、配置和启动说明(如果需要的话)。组件命名的约定可以在每个组件中看到。
太长慎读 把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
$q
) 而非回调。这将使你的代码更加优雅、直观,并且免于回调地狱。$resource
而非 $http
。更高的抽象可以避免冗余。gulp.src("./src/*.js")
.pipe(wrap(‘(function(){\n"use strict";\n<%= contents %>\n})();‘))
.pipe(gulp.dest("./dist"));
$scope
。仅添加与视图相关的函数和变量。ngInit
。ngInit
只有在一种情况下的使用是合适的:用来给 ngRepeat
的特殊属性赋予一个别名。除此之外, 你应该使用 controllers 而不是 ngInit
来初始化scope变量。ngInit
中的表达式会传递给 Angular 的$parse
服务,通过词法分析,语法分析,求值等过程。这会导致:
$parse
服务中对表达式的缓存基本不起作用,因为 ngInit
表达式经常只有一次求值$
前缀来命名变量, 属性和方法. 这种前缀是预留给 AngularJS 来使用的.module.factory(‘Service‘, function ($rootScope, $timeout, MyCustomDependency1, MyCustomDependency2) {
return {
//Something
};
});
模块应该用驼峰式命名。为表明模块 b
是模块 a
的子模块, 可以用点号连接: a.b
。
有两种常见的组织模块的方式:
当前并无太大差别,但前者更加清晰。同时,如果 lazy-loading modules 被实现的话 (当前并未列入 AngularJS 的路线图),这种方式将改善应用的性能。
Ctrl
结尾。HomePageCtrl
, ShoppingCartCtrl
,AdminPanelCtrl
, 等等)。使用以下语法定义控制器:
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
完成的操作(比如$scope.$broadcast
)时,移除掉了 $scope
,就是为 Angular2 做好准备。想深入了解 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();
};
});
为什么控制器不应该包含业务逻辑和应用状态?
需要进行跨控制器通讯时,通过方法引用(通常是子控制器到父控制器的通讯)或者 $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>
scope
而非 $scope
。在 compile 中, 你已经定义参数的 post/pre link functions 将在函数被执行时传递, 你无法通过依赖注入改变他们。这种方式同样应用在 AngularJS 项目中。ng
或 ui
前缀,因为这些备用于 AngularJS 和 AngularJS UI。scope.$on(‘$destroy‘, fn)
来清除。这点在使用第三方指令的时候特别有用。$sce
。$digest
loop 中被频繁调用,过于复杂的过滤器将使得整个应用缓慢。这个部分包含了 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-translate
。优化 digest cycle
$digest
循环(例如:在进行实时通讯时,不要在每次接收到消息时触发$digest
循环)。bindonce
(对于早期的 AngularJS)。如果是 AngularJS >=1.3.0 的版本,应使用Angular内置的一次性数据绑定(One-time bindings).$watch
中的运算简单。在单个 $watch
中进行繁杂的运算将使得整个应用变慢(由于JavaScript的单线程特性,$digest
loop 只能在单一线程进行)$watchCollection
, 对监听的表达式和之前表达式的值进行浅层的检测.$timeout
回调函数所影响时,在 $timeout
设置第三个参数为 false 来跳过 $digest
循环.用打包、缓存html模板文件到你的主js文件中,减少网络请求, 可以用 grunt-html2js / gulp-html2js. 详见 这里 和 这里 。 在项目有很多小html模板并可以放进主js文件中时(通过minify和gzip压缩),这个办法是很有用的。
标签:
原文地址:http://www.cnblogs.com/liaoshiqi/p/5900302.html