本文章是针对于刚开始学习angularJS的菜鸟们,根据(精通angularJS一书)做的精简版读书笔记, 旨在通过笔记让更多的前端开发,在一周之内可以入门AngularJS。
第一章 速成
-
1.快速使用
- a.导入angularJS 库文件 (线上库或者离线库都可以,如果本地测试建议使用离线库,因为速度不是快一点点哦)
- b.在html或者body DOM标签上添加 ng-app,表示这是一个angularJS的应用
- c.初始化一个模型,并使用。ng-init初始化模型,并通过表达式 {{name}}传递值
<!DOCTYPE html>
<html>
<head>
<title></title>
<!-- 线上版本:https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.js
脱机版本,请从https://angularjs.org/ 下载angular.js并且放在html用目录下 -->
<script type="text/javascript" src='angular.js'></script>
</head>
<body ng-app ng-init="name='World'">
<!-- input 的改变会直接反应在 下面的<p>-->
<input type="text" name="" ng-model="name">
<p>Hello, {{name}}</p>
</body>
</html>
hello world的例子没有明显的分层策略:数据初始化、逻辑和视图都混在了同一个文件中。通常情况我们希望把逻辑控制放倒一个controller里(ng-controller)来取而代之ng-init
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" src='angular.js'></script>
<script type="text/javascript">
// 1.3.0-beta.15 以后的版本不能直接定义controller,必须先定义model,再注入controller
var myModel = angular.module('myapp1', []).controller('myController', function($scope) {
$scope.name = 'eric';//$scope对象是末班的域模型
});
</script>
</head>
<!-- 必须要声明ng-app,不能使用匿名。myApp1对应于module名字-->
<body ng-app='myapp1'>
<div ng-controller="myController">
<input type="text" name="" ng-model="name">
<p>Hello, {{name}}</p>
</div>
</body>
</html>
2.作用域层级
每个$scope都是Scope类实例,$scope类拥有很多方法,用于控制作用域的生命周期、提供事件传播功能,一级支持模板的渲染等。
function($scope){} 只有参数,它是从何而来呢?
答案是ng-controller指令会去调用scope对象的$new()方法创建新的作用域$scope。看上去需要至少一个作用域实例才能创建新的作用域。确实如此,AngularJS拥有$rootScope,它是其他所有作用域的父作用域,将在新应用启动时自动创建
ng-controller指令是作用域创建指令,DOM树中遇到作用域创建指令时,AngularJS都会闯将Scope类的新实例$scope,新创建的$scope会拥有$parent属性,并指向它的父作用域。
某些指令会创建子作用域,如ng-repeat,对应每个country都有个新变量要暴露给$scope而且不能覆盖之前变量的值。AngularJS为了解决此问题,给集合中每个元素创建新的作用域。这些作用域会组成类似DOM树 层级结构,用Chrome的插件Batarang可以查看
<!DOCTYPE html>
<html>
<head>
<title></title>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript">
angular.module('myApp', []).controller('myController', function($scope) {
$scope.countries = [{
name: '中国',
population: '13亿'
}, {
name: '日本',
population: '1亿'
}];
});
</script>
</head>
<body ng-app='myApp'>
<div ng-controller='myController'>
<ul>
<li ng-repeat='country in countries'>
{{country.name}} has {{country.population}}
</li>
</ul>
</div>
</body>
</html>
作用域中定义的属性对所有子作用域是可见的,只要子作用域没有定义同名属性。AngularJS中的作用域继承和Javascript的原型继承遵循同样的规则(沿继承树向上查找属性,直到倒找为止)。
在进行read access的时候作用域层级的继承关系直观易懂。然而,进行write access是的情况就有些复杂了。访问父作用域的属性,就必须使用
parent.name(尽量避免使用
parent属性,因为塔在AngularJS表达式和模板创建的DOM结构间建立了强关联)
推荐解决方案是将变量绑定为某个对象的属性,而不是直接绑定作用域的属性。如:
<!--错误的方式,子作用域的name和父作用域冲突覆盖之-->
<body ng-app='myapp1' ng-init="name='world'">
<h1>{{name}}</h1>
<div ng-controller="myController">
<input type="text" name="" ng-model="name">
<p>Hello, {{name}}</p>
</div>
</body>
<!--正确的方式-->
<body ng-app='myapp1' ng-init="thing={name:'world'}">
<h1>{{thing.name}}</h1>
<div ng-controller="myController">
<input type="text" name="" ng-model="thing.name">
<p>Hello, {{thing.name}}</p>
</div>
</body>
作用域通常在不需要的时候会被销毁(垃圾回收),也可以通过调用Scope类上的 new()和 destroy()方法,手动创建和销毁作用域。
3.事件系统
事件可以从任何作用域开始分发(dispatch)。然后向上分发(
emit)或者向下广播(
broadcast).
AngularJS的核心服务与指令通过这趟事件列车来发送信号,通知应用状态的重要变化。例如,可以监听$locationChangeSuccess事件(由4rootDvope广播),当浏览器url变化时我们可以收到如下通知。
$scope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl){//做出相应变化});
//$on方法,用于注册作用域事件的处理器。与DOM事件一样event对象有preventDefault()和stopPropagation()方法。
AngularJS中,仅有三个事件可以被向上分发(emitted)
- $includeContentRequested
- $includeContentLoaded
- $viewContentLoaded
七个事件可以被向下广播(broadcasted) - $locationChangeStart
- $locationChangeSuccess
- $routeUpdate
- $routeChangeStart
- $routChangeSuccess
- $routeChangeError
- $destroy
4.模块与依赖注入
全局定义的控制器构造函数,只在简单代码示例和快速制作原型时才有用,永远不要在复杂的真是应用中使用全局定义的控制器。
让我们看看如何将丑陋的,全局定义的控制器模块化:
//模块化之前,mycontroller是全局函数
var mycontroller = function($scope) {
$scope.name = 'eric';
}
//模块化之后,mycontroller只应用于ng-app=‘myapp1’的模块
var myModel = angular.module('myapp1', []).controller('myController', function($scope) {
$scope.name = 'eric';
});
依赖注入第一步,是将对象注册在module上,我们不直接注册对象的实例,而是将对象创建的方案抛给依赖注入系统,而后AngularJS解释这些方案以初始化对象,并在之后连接它们,最后成为可运行的应用。
AngularJS的 provide服务可以注册在不同的对象创建方案。之后 injector服务会解释这些方案,生成玩呗二可用的对象实例(已经解决好所有的依赖关系)
$injector服务创建的对象称之为服务(service)。在整个应用的生命中,每种方案AngularJS仅解释一次,也就是说,每个对象仅有一个实例。
原型继承
var NotificationService = function (){
this.MAX_LEN = 10;
this.notificationsArchive = new NotificationsArchive();
this.notifications = [];
};
NotificationService.prototype.push = function(notification){
var newLen, notificationToArchive;
newLen = this.notification.unshift(notification);//unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
if(newLen > this.MAX_LEN){
notificationToArchive = this.notifications.pop();
this.notificationsArchive.archive(notificationToArchive);
}
};
NotificationService.prototype.getCurrent = function(){
return this.notification;
};
Value注册
注册已经初始化好的对象,缺点是被注册的值必须是已经实例化好,并且不依赖于其他对象的
//angularJS值(value)注入
var myapp1 = angualr.module('myapp1',[]);
myapp1.value('notificationsArchive', new NotificationsArchive());
Service注册
NotificationService依赖于notificationsArchive,所以他不能注册为值对象,
//angularJS(serivce)服务注入,通过依赖注入,在NotificationService构造函数中可以消除new关键字,意味着此服务已经不依赖于其他对象的初始化,可以接受哦任何归档服务,这让应用变得非常灵活。
myapp1.service('notificationService',NotificationService);
//NotificationService构造函数
var NotificationService = function(notificationsArchive){
this.notificationsArchive = notificationsArchive;
}
Factory注册
另一条注册对象创建方案的途径是使用factory方法,任何能够创建对象的函数都可以被注册,因此它想必service而言更加灵活。factory是让对象加入依赖注入系统最常用的方法,它十分灵活,又能实现复杂的对象创建逻辑。
myapp1.factory('NotificationService', function(notificationsArchive) {
this.MAX_LEN = 10;
this.notifications = [];
var ret = {
push: function(notification) {
var newLen, notificationToArchive;
newLen = this.notification.unshift(notification); //unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
if (newLen > this.MAX_LEN) {
notificationToArchive = this.notifications.pop();
notificationsArchive.archive(notificationToArchive);
}
},
getCurrent: function() {
return this.notification;
}
}
});
constant注册
//理想状态下应该这样子获取配置,而不是写死在函数中
myapp1.factory('NotificationService', function(notificationsArchive, MAX_LEN) {...});
myapp1.constant('MAX_LEN', 10);
Provider
以上所有注册方法,都是最通用方法provider的特殊类型,下面是provider注册notificationsService的例子
//provider是一个函数,它返回包含$get属性的对象,改属性是一个工厂函数,它被调用时会返回service的实例,在$get方法被调用前,还能设置配置信息。实际上,我们能够设置maxLen
myapp1.provider('notificationsService', function() {
var config = {
maxLen: 10
};
var notifications = [];
return {
setMaxLen: function(max) {
config.maxLen = max || config.maxLen;
},
$get: function(notificationsArchive) {
return {
{
push: function(notification) {
var newLen, notificationToArchive;
newLen = this.notifications.unshift(notification); //unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
if (newLen > config.MAX_LEN) {
notificationToArchive = this.notifications.pop();
notificationsArchive.archive(notificationToArchive);
}
},
getCurrent: function() {
return this.notification;
}
}
}
}
}
});
模块的生命周期
如前所属,angularJS支持多装对象创建方案,provider是其中的通用方法,它在创建对象实例前可以对其进行配置。为了支持provider,angularJS将模块生命周期分为两个阶段,
1. 配置阶段
2. 运行阶段
//这里config对notificationServiceProvider有依赖,表明他是即将执行的对象创建方案,配置阶段允许我们对他进行最后的调整
myapp1.config(function(notificationSerivceProvider){
notificationSerivceProvider.setMaxLen(5);
});
//为了说明运行阶段的作用,我们假设要显示应用的已运行时间给用户。为了实现目标,我们将应用的已运行事假定义为$rootScope实例的属性,代码如下:
myapp1.run(function($rootScope){
$rootScope.appstart = new Date();
})
模块的生命周期
- | 对象种类 | 可以在配置阶段注入 | 可以在运行阶段注入 |
---|---|---|---|
Constant | 常量值 | Yes | Yes |
Variable | 变量值 | - | Yes |
Service | 构造函数创建的新对象 | - | Yes |
Factory | 工厂函数返回的新对象 | - | Yes |
Provider | $get工厂函数创建的新对象 | Yes | - |
模块依赖注入
//通知服务和归档服务,可以迁移到它们自己的模块中去(分别命名为notifications和archive)
angular.module('application',['notifications','archive'])
跨模块的可见性
1. 定义在子模块的服务可以注入父模块
angular.module('app', ['engines'])
.factory('car', function($log, dieselEngine) {
return {
start: function() {
$log.info('starting' + dieselEngine.type);
}
}
});
angular.module('engines', [])
.factory('dieselEngine', function() {
return { type: 'diesel' };
});
- 兄弟模块的服务也互相可见
angular.module('app', ['engines', 'cars']);
angular.module('cars', [])
.factory('car', function($log, dieselEngine) {
return {
start: function() {
$log.info('starting' + dieselEngine.type);
}
}
});
angular.module('engines', [])
.factory('dieselEngine', function() {
return { type: 'diesel' };
});
- 应用中的服务是不能被重名的,父模块的服务会被子模块中的同名模块覆盖。
5. 总结AngularJS 于JQuery
AngularJS内嵌jQuery的一个简化版本jqLite(是一个微小子集,只专注于操纵DOM的例程routine),内嵌jqLite后,AngularJS不再依赖其他外部库。
jQuery与AngularJS虽然可以合作,但是AngularJS围绕模型(由模型的变化来驱动模板(声明式)来更新UI),jQuery围绕DOM,它们是彻底不同的开发范型。