My Little World

AngularJS issue

#$digest和$apply

当调用$digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用$apply的时候,会触发作用域树上的所有监控。

因此,从性能上讲,如果能确定自己作的这个数据变更所造成的影响范围,应当尽量调用$digest,只有当无法精确知道数据变更造成的影响范围时,才去用$apply,很暴力地遍历整个作用域树,调用其中所有的监控。

视图模型的层次

在Angular中,存在作用域的继承。所谓作用域的继承,是指:如果两个视图有包含关系,内层视图对应的作用域可以共享外层视图作用域的数据。
以下情况会造成嵌套作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 1. controller 套嵌
如果两个控制器所对应的视图存在上下级关系,它们的作用域就自动产生继承关系
html:
<body ng-app="test">
<div ng-controller="OuterCtrl">//父域
<span ng-bind="a"></span>
<div ng-controller="InnerCtrl">//子域
<span ng-bind="a"></span>
<span ng-bind="b"></span>
</div>
</div>
</body>
js:
var app = angular.module("test", []);

app.controller("OuterCtrl", function ($scope) {
$scope.a = 1;
});

app.controller("InnerCtrl", function ($scope) {
$scope.b = 100;

$scope.increasea = function() {
$scope.a++;
};
});

//2.数组和对象属性迭代的时候,循环的每个元素都建立了单独的作用域
<ul>
<li ng-repeat="member in members">{{member.name}} in {{teamname}}</li> //每一个都是子域,但可以共享父域的teamname
</ul>

//3.使用ng-if,ng-include,ng-view等指令时构建的DOM,会形成自己的子域

inner.html
<div>
<span ng-bind="name"></span>
</div>

outer.html
<div ng-controller="OuterCtrl">
<span ng-bind="name"></span>
<div ng-include="'inner.html'"></div>//ng-include会创建一层作用域,inner.html里面bind的name会使用OuterCtrl这个父域里面的name
</div>
function OuterCtrl($scope) {
$scope.name = "outer name";
}

作用域继承的缺点就是,子域绑定值能继承父域同名绑定值的值,但是一旦子域绑定值,因为某种触发函数发生改变,父域同名绑定值不会随之改变
虽然可以通过$parent获取父域的同名绑定值,然后子域中做到同时修改,但如果在代码中使用了这种方式,意味着视图模型也只能这样包含,如果再中间插一层子域,
则同名关系变成$parent.$parent,使关系进一步复杂
所以,应当尽量避免父子作用域存在同名变量的情况

从可重用性角度来看,如果满分5分的话,整个应用的这些部分的得分应当是这样:

服务,比如说,对后端RESTful接口的AJAX调用,对本地存储的访问等,5分
控制器(也就是视图模型),2-3分
指令,这个要看情况,有的指令是当作对HTML元素体系的扩展来用的,有些是其他事情的
纯UI类型的指令,也可以算是控件,比如DatetimePicker,5分
有些用于沟通DOM跟视图模型的指令,2分
界面模板,这个基本就没有重用性了,1分

模块机制-module的坑

1.覆盖问题
如果moduleA,同时依赖moduleB和moduleC,而moduleB和moduleC有同名的factoryD,那在moduleA使用factoryD的时候,
根据依赖顺序,如果moduleB在前,则moduleC的factoryD会覆盖moduleB的factoryD,moduleA使用的是moduleC的factoryD
如果在以上依赖情况基础上,moduleA自己又定义了同名factoryD,那么moduleA自己的factoryD会覆盖moduleC的factoryD

2.module不支持运行时添加依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
angular.module("some.components", [])
//这里定义了一些组件
;
//上面是一个组件库,集中存放于components.js中,要在自己的应用中使用,必须:
angular.module("our.app", ["some.components"]);

//不可以这样
angular.module("our.app", []);

require("components.js", function() {
// angular.module("our.app").addDependency("some.components");
// ready to use
});

表达式

1.在绑定表达式里面,只能使用自定义函数,不能使用原生函数

1
<div>{{abs(-1)}}</div>//不允许这样使用

如果确实需要调用原生函数,可以用一个自定义函数作包装,在自定义函数里面可以随意使用各种原生对象
绑定表达式里可以使用自定义函数,但如果只是对数据做简单处理可以使用过滤器

2.数组里有重复元素的情况,ng-repeat代码不能起作用,原因是Angular默认需要在数组中使用唯一索引
可以指定它使用序号作索引

1
2
3
4
$scope.arr2 = [1, 1, 3];
<ul>
<li ng-repeat="item in arr2 track by $index">{{item}}</li>
</ul>

但对象值有重复的话,不用像数组那么麻烦需要指定$index做索引,因为它是对象的key做索引,是不会重复的。

事件流

angular事件流是一个通知广播的过程
现有A、B两个视图,每个视图又包含各自两个子视图,如果A视图的A1子视图想通知B视图的子视图B1一个业务事件(或传递某个消息)
那A1就发出业务通知,这个通知会沿着父作用域一路往上到达双方共同的祖先作用域
然后这个通知会以广播的形式从祖先作用域一级一级往下进行广播,直到到达需要的地方
issue1
相关事件:
从作用域往上发送事件,使用scope.$emit。

1
$scope.$emit("someEvent", {});

从作用域往下发送事件,使用scope.$broadcast

1
$scope.$broadcast("someEvent", {});

这两个方法的第二个参数是要随事件带出的数据。
这两种方式传播事件,事件的发送方自己也会收到一份。
无论是$emit还是$broadcast发送的事件,都可以被接收,接收这两种事件的方式是一样的:

1
2
3
$scope.$on("someEvent", function(e) {
// 这里从e上可以取到发送过来的数据
});

如果想要阻止$emit事件的继续传播,可以调用事件对象的stopPropagation()方法

1
2
3
$scope.$on("someEvent", function(e) {
e.stopPropagation();
});

想要阻止$broadcast事件的传播,首先,调用事件对象的preventDefault()方法,
然后,在收取这个事件对象的时候,判断它的defaultPrevented属性,如果为true,就忽略此事件。
这个过程比较麻烦,其实一般是不需要管的,只要不监听对应的事件就可以了。
在实际使用过程中,也应当尽量少使用事件的广播,尤其是从较高的层级进行广播
上级作用域

1
2
3
$scope.$on("someEvent", function(e) {
e.preventDefault();
});

下级作用域

1
2
3
4
5
$scope.$on("someEvent", function(e) {
if (e.defaultPrevented) {
return;
}
});

事件总线

事件流的通知过程效率低,而且很多下级视图可能根本不要接收消息,进行多余的广播,
所以组建订阅发布模式,接收方在这里订阅消息,发布方在这里发布消息,这个地方就是事件总线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
app.factory("EventBus", function() {
var eventMap = {};

var EventBus = {
on : function(eventType, handler) { //订阅事件
//multiple event listener
if (!eventMap[eventType]) {
eventMap[eventType] = [];
}
eventMap[eventType].push(handler);
},

off : function(eventType, handler) {
for (var i = 0; i < eventMap[eventType].length; i++) {
if (eventMap[eventType][i] === handler) {
eventMap[eventType].splice(i, 1);
break;
}
}
},

fire : function(event) { //发布事件
var eventType = event.type;
if (eventMap && eventMap[eventType]) {
for (var i = 0; i < eventMap[eventType].length; i++) {
eventMap[eventType][i](event);
}
}
}
};
return EventBus;
});