angular作用域理解
作用域在一个angular应用中是以树形体现的,根作用$rootScope就位于最顶层,从它往下挂着各级作用域.每一级作用域上面挂着变量和方法,供所属的视图调用.可以这么说每个controller都会有自己的scope,所有的scope都是属于$rootScope的子或者子的子...
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<input type="text" ng-model="a"/>
<div>{{a}}</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
});
</script>
</body>
</html>
如果想在控制器中使用根作用域,可以注入$rootScope.
作用域的继承关系
开发中,我们经常遇到控制器的嵌套.
我们来看两个例子
1:变量
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
{{b}}
<div ng-controller="OuterCtrl">
<span>{{a}}</span>
<div ng-controller="InnerCtrl">
<span>{{a}}</span>
</div>
</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.a = 1;
})
.controller('OuterCtrl', function($scope) {
$scope.b = 4444;
})
.controller('InnerCtrl', function($scope) {
})
</script>
</body>
</html>
2方法:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
{{b()}}
<div ng-controller="OuterCtrl">
<span>{{a()}}</span>
<div ng-controller="InnerCtrl">
<span>{{a()}}</span>
</div>
</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.a = function(){
return 1
}
})
.controller('OuterCtrl', function($scope) {
$scope.b = function(){
return 444
}
})
.controller('InnerCtrl', function($scope) {
})
</script>
</body>
</html>
结果:上面两个例子页面上出现两个"1".
说明:子控制可以访问它的祖先控制器中的变量和方法.
在angular中,如果两个控制器所对应的视图存在上下级关系,它们的作用域就自动产生继承关系.以此类推,整个angular应用的作用域,都存在自顶向下的继承关系,最顶部的是$rootScope,然后一级一级,沿着不同的控制器往下,形成了一颗作用域树.
简单变量的取值与赋值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-controller="OuterCtrl">
<span>OuterCtrl: {{a}}</span><br/>
<div ng-controller="InnerCtrl">
<span>InnerCtrl: {{a}}</span> <br/>
<button ng-click="a=a+1">InnerCtrl--a++</button>
</div>
</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.a = 1;
})
.controller('OuterCtrl', function($scope) {
})
.controller('InnerCtrl', function($scope) {
})
</script>
</body>
</html>
结果:点击按钮,一个在变另一个不变
取值的时候,因为inner自身上面没有,所以就要沿着树形往上找,找到了1.然后就赋值了1,这么说来它的a就有值了.当自增的时候就可以自己赋值给自己.
总结一句话:"赋值自身没有往上找,赋值后只改变自身".
对象在上下级作用域之间的共享
上下级共享变量.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-controller="OuterCtrl">
<span>OuterCtrl: {{data.a}}</span><br/>
<div ng-controller="InnerCtrl">
<span>InnerCtrl: {{data.a}}</span> <br/>
<button ng-click="data.a=data.a+1">InnerCtrl--a++</button>
</div>
</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.data = {
a: 1
};
})
.controller('OuterCtrl', function($scope) {
})
.controller('InnerCtrl', function($scope) {
})
</script>
</body>
</html>
结果:点击按钮,两个变量值都改变了
为什么这次的结果和上面的会不一样呢?
因为两者的data是同一个引用,对这个对象中的属性值作修改,是可以反映到两级对象上的.
但如果你还是想通过变量而不是对象的形式,那么可以通过$parent来指定上级作用域
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-controller="OuterCtrl">
<span>OuterCtrl: {{a}}</span><br/>
<div ng-controller="InnerCtrl">
<span>InnerCtrl: {{a}}</span> <br/>
<button ng-click="$parent.a=a+1">InnerCtrl--a++</button>
</div>
</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.a=1
})
.controller('OuterCtrl', function($scope) {
})
.controller('InnerCtrl', function($scope) {
})
</script>
</body>
</html>
控制器实例别名
在angular 1.2开始,引入了控制器实例别名机制.
在此之前,都是需要向控制器注入$scope,然后通过它来定义控制器里的属性和方法.
下面这个例子就是不用$scope改用"this":
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl as instanceB">
<div ng-controller="OuterCtrl">
<span>OuterCtrl: {{instanceB.a}}</span><br/>
<div ng-controller="InnerCtrl">
<span>InnerCtrl: {{instanceB.a}}</span> <br/>
<button ng-click="instanceB.a=instanceB.a+1">InnerCtrl--a++</button>
</div>
</div>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function() {
this.a=1
})
.controller('OuterCtrl', function() {
})
.controller('InnerCtrl', function() {
})
</script>
</body>
</html>
这个例子中,通过as 给 ng-controller实例取了一个别名叫做instanceB,这样,它里面的各级视图就可以显式使用这个名称来调用控制器里的属性和方法了.
不请自来的新作用域
在一个应用用,ng-controller都会创建一个新的作用域,这是因为它会实例化一个新的控制器,往里面注入一个$scope,也是一个新的作用域.这个很好理解,但是有些情况就不是那么容易理解了,比如说,ng-repeat.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<ul>
<li ng-repeat="item in arr track by $index">{{item}}</li>
</ul>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.arr = [1, 2, 3];
})
</script>
</body>
</html>
在ng-repeat 的表达式中有个item,在这里,数组中有三个元素,在循环的时候这三个元素都叫做item.那么问题就来了,如何区分每个不同的item?
我们换个更能说明问题的例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<div>outer: {{sum1}}</div>
<ul>
<li ng-repeat="item in arr track by $index">
{{item}}
<button ng-click="sum1=sum1+item">increase</button>
<div>inner: {{sum1}}</div>
</li>
</ul>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.sum1=0;
$scope.arr = [1, 2, 3];
})
</script>
</body>
</html>
运行这个例子,我们就会发现每个item都会独立改变,这说明它们是区分开来的.
在angular中,angular会为ng-repeat的每个子项都创建单独的作用域,所以每个item都存在于自己的作用域里,互不影响.
但有时候,我们是需要在循环内部访问外层变量.
我们举例说,如果两个控制器,它们的视图有包含关系,内层控制器的作用域可以通过$parent来访问外层控制器作用域上的变量,那么,在这种循环里,是不是也可以如此呢?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<div>outer: {{sum1}}</div>
<div>outer: {{sum2}}</div>
<ul>
<li ng-repeat="item in arr track by $index">
{{item}}
<button ng-click="$parent.sum2=sum2+item">increase</button>
<div>inner: {{sum2}}</div>
</li>
</ul>
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.sum1=0;
$scope.sum2=0;
$scope.arr = [1, 2, 3];
})
</script>
</body>
</html>
果然是可以的。很多时候 ,人们会把$parent误认为是上下两级控制器之间的访问通道,但从这个例子我们可以看到,并非如此,只是两级作用域而已,作用域跟控制器还是不同的,刚才的循环可以说是有两级作用域,但都处于同一个控制器之中。
现在我们知道了ng-controller和ng-repeat都会创建新的作用域,除此之外,还有一些指令也会创建新的作用域,下面我继续来看.
我们简单回顾下ng-show/ng-hide/ng-if.
ng-show/ng-hide:就是某个东西本来就有,只是控制它显示还是隐藏.
ng-if:如果满足条件,就创建这块dom,否则就不会创建.所以ng-if所控制的dom块,只有在条件为真时才会存在.
ng-show/ng-hide是不自带作用域的,而ng-if则自己创建了一级作用域.
在用的时候,两者就是有差别的,比如说内部元素访问外层定义的变量,就需要使用类似ng-repeat那样的$parent语法了。
总结:相似的类型还有ng-switch,ng-include等等,规律可以总结,也就是那些会动态创建一块界面的东西,都是自带一级作用域。
"悬空"的作用域
一般而言,angular应用中,基本是不需要手动创建作用域的,但真有这个需要也是可以的.
在任意已有的作用域上调用$new(),就能创建一个新的作用域.
var newScope = scope.$new();
刚创建出来的作用域是一个"悬空"作用域,也就是说,它跟任何界面模板都不存在绑定关系,创建它的作用域就会成为它的$patent.这种创建出来的作用域可以经过$compile阶段,与某视图模板进行融合.
为了帮助理解我们这里再举一个例子.
比如我们利用JQ里面的创建了一个dom节点,但是它是不在dom树上的,只有当它被append添加到dom树上,才能被当做普通的dom来使用.
<script type="text/javascript">
$(function(){
var $li_1 = $("<li></li>"); // 创建第一个<li>元素
var $li_2 = $("<li></li>"); // 创建第二个<li>元素
var $parent = $("ul"); // 获取<ul>节点。<li>的父节点
$parent.append($li_1); // 添加到<ul>节点中,使之能在网页中显示
$parent.append($li_2); // 可以采取链式写法:$parent.append($li_1).append($li_2);
});
</script>
那么,悬空的作用域是不是什么用处都没有呢?也不是,尽管它未与视图关联,但是它的一些方法仍然可以用。
angular中的$watch,就是定义在作用域原型上.
如果我们想要监控一个数据的变化,但这个数据并非绑定到界面上的,下面给出个例子说明:
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
var child = {
a: 1
};
child.a++;
})
</script>
注意这个变量child,它是没有绑定到$scope上的,如果我们想要在a变化的时候做某些事情,是没有办法做的.
但是我们的$watch和$eval之类的方法,其实都是实现在作用域对象上的,也就是说,任何一个作用域,即使没有与界面产生关联,也是能够使用这些方法的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
var child = $scope.$new();
child.a = 1;
child.$watch("a", function(newValue) {
alert(newValue);
});
$scope.change = function() {
child.a++;
};
})
</script>
</body>
</html>
这时候child里面a的变更就可以被观测到,并且,这个child只有本作用域可以访问到,相当于是一个增强版的数据模型。
作用域上的事件
我们刚提到用$parent来处理上下级的通讯,但这不是一种好的方式,尤其在不同的控制器之间,这会增加它们的耦合,对组件复用很不利.
提到事件,可能很多人想到的都是DOM事件,其实DOM事件只存在于上层,而且没有业务含义,如果我们想要传递一个明确的业务消息,就需要使用业务事件。这种所谓的业务事件,其实就是一种消息的传递。
比如说,某军队中,1营1连1排长想要给1营2连下属的三个排发个警戒通知,他的通知方向是一级一级向上汇报,直到双方共同的上级,也就是1营指挥人员这里,然后再沿着二连这个路线向下去通知。
1:从作用域往上发送事件,使用scope.$emit
$scope.$emit("someEvent", {});
2:从作用域往下发送事件,使用scope.$broadcast
$scope.$broadcast("someEvent", {});
这两个方法的第二个参数是要随事件带出的数据。
注意,这两种方式传播事件,事件的发送方自己也会收到一份。
使用事件的主要作用是消除模块间的耦合,发送方是不需要知道接收方的状况的,接收方也不需要知道发送方的状况,双方只需要传送必要的业务数据即可。
使用事件的主要作用是消除模块间的耦合,发送方是不需要知道接收方的状况的,接收方也不需要知道发送方的状况,双方只需要传送必要的业务数据即可。
事件的接收和阻止
无论是$emit还是$broadcast发送的事件,都可以被接收,接收这两种事件的方式是一样的:
$scope.$on("someEvent", function(e) {
// 这里从e上可以取到发送过来的数据
});
注意了,事件被接收了,并不代表它就终止了,它仍然会沿着原来的方向继续传播,也就是:
- $emit的事件将继续向上传播
- $broadcast事件将继续向下传播
如果我们希望某一级接收到事件后,就让它终止,不再传播.但是值得注意的是
只有$emit发出的事件才可以被终止,$broadcast发出的是不可以被终止的.
如果要阻止$emit事件继续传播,可以在接收地方调用事件对象的stopPropagation()方法.
$scope.$on("someEvent", function(e) {
e.stopPropagation();
});
我们前面说过$broadcast发出的事件是不可以被终止的.办法也不是没有,只是稍微有点麻烦.
首先,调用事件对象的preventDefault()方法,然后,在收取这个事件对象的时候,判断它的defaultPrevented属性,如果为true,就忽略此事件。这个过程比较麻烦,其实我们一般是不需要管的,只要不监听对应的事件就可以了。在实际使用过程中,也应当尽量少使用事件的广播,尤其是从较高的层级进行广播。
上级作用域
$scope.$on("someEvent", function(e) {
e.preventDefault();
});
下级作用域
$scope.$on("someEvent", function(e) {
if (e.defaultPrevented) {
return;
}
});
在Angular中,不同层级作用域之间的数据通信有多种方式,可以通过原型继承的一些特征,也可以收发事件,还可以使用服务来构造单例对象进行通信。
前面提到的这个军队的例子,有些时候沟通效率比较低,特别是层级多的时候。想象一下,刚才这个只有三层,如果更复杂,一个排长的消息都一定要报告到军长那边再下发到其他基层主官,必定贻误军情,更何况有很多下级根本不需要知道这个消息。
前面提到的这个军队的例子,有些时候沟通效率比较低,特别是层级多的时候。想象一下,刚才这个只有三层,如果更复杂,一个排长的消息都一定要报告到军长那边再下发到其他基层主官,必定贻误军情,更何况有很多下级根本不需要知道这个消息。
Angular控制器和指令的交互
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script src="http://apps.bdimg.com/libs/angular.js/1.3.13/angular.js"></script>
<script>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function ($scope) {
$scope.name = true;
});
app.directive('myDirective', function ($compile) {
return {
restrict: 'AE', //attribute or element
scope: {
myDirectiveVar: '=',
//bindAttr: '='
},
template: '<button>' +
'click me</button>',
replace: true,
//require: 'ngModel',
link: function ($scope, elem, attr, ctrl) {
elem.bind('click', function () {
$scope.$apply(function () {
$scope.myDirectiveVar = !$scope.myDirectiveVar;
alert($scope.myDirectiveVar);
});
});
//var textField = $('input', elem).attr('ng-model', 'myDirectiveVar');
// $compile(textField)($scope.$parent);
}
};
});
</script>
</head>
<body ng-app="plunker" ng-controller="MainCtrl">
This scope value <input type="checkbox" ng-model="name">
<my-directive my-directive-var="name"></my-directive>
</body>
</html>
小结:一个大型单页应用,需要对部件的整合方式和通信机制作良好的规划,为它们建立良好的秩序,这对于确保整个应用的稳定性是非常必要的。
angular控制器之间的通信方法概括:
一:通过事件event
$sope.$emit:通过监听$rootScope的事件获取参数;
$rootScope.$broadcast:通过监听$scope的事件获取参数.
二:通过service
可以创建一个service,将数据存储到这个service中,并且设置相应的getter/setter.
1:service.setter(...)在改完数据后可以$emit('data-update')
controller里的$on('data-update',function(){$scope.data=service.getData()})
这个是保存数据最好的方法了.
三:通过$rootScope
这个方法不太建议用,但是它用的比较方便,也就是把数据存在$rootScope中,这样各个子$scope 都可以调用了,不过需要注意下它的生命周期.
四:直接使用$scope.$$nextSibling及类似的其他一些属性
这方法官方不建议使用,这里就不介绍 了.
出自文档
链接:
- http://www.zhihu.com/question/34977234
- https://github.com/xufei/blog/issues/18