做用域是控制器与视图,指令与视图之间的粘合剂。css
做用域能够作哪些事情:html
做用域的分类:根做用域($rootScope)、父做用域、子做用域、独立做用域(存在于自定义指令中)。angularjs
AngularJS存在四种做用域:数组
(这里不讨论自定义指令的做用域).app
在AngularJS中,一个scope跟一个元素关联,而一个元素不是必须直接跟一个scope关联。元素经过如下三种方式被分配一个scope:ide
在AngularJS的上下文中查看某一元素上分配的scope:函数
打开一个AngularJS项目,打开控制台,选中你想要查看的那个元素,而后在控制台中输入如下代码:post
angular.element($0).scope();
scope的一些内置属性:测试
下面是例子ui
ng-include – 建立子做用域
html代码(这里要注意ng-include的两种写法):
<!DOCTYPE html> <html lang="en" ng-app="scopeApp"> <head> <meta charset="UTF-8"> <title>Scope</title> <script src="js/angular.js"></script> </head> <body> <!--ng-include--> <div ng-controller="fifthCtrl"> {{name}} <div ng-include="'tpls/include-0.html'"></div> <script type="text/ng-template" id="tpls/include-0.html"> <p>this is a test {{name}}</p> <input type="text" ng-model="name"/> <input type="text" ng-model="$parent.name"/> </script> </div> <script src="script/app.js"></script> </body> </html>
js代码:
var directiveApp = angular.module('directiveApp',[]); directiveApp.controller('fifthCtrl',['$rootScope','$scope',function($rootScope,$scope) { $scope.name = 'thie fifth'; }]);
ng-repeat – 建立子做用域
html代码:
<!DOCTYPE html> <html lang="en" ng-app="directiveApp"> <head> <meta charset="UTF-8"> <title>Directive</title> <script src="js/angular.js"></script> </head> <body> <!--ng-repeat--> <div ng-controller="sixedCtrl"> <h3>显示arr</h3> <p>{{arr[0]}}</p> <p>{{arr[1]}}</p> <p>{{arr[2]}}</p> <h3>显示brr</h3> <p>{{brr[0].name}}</p> <p>{{brr[1].name}}</p> <p>{{brr[2].name}}</p> <ol> <li ng-repeat="item in arr"> <input type="text" ng-model="item"/> </li> </ol> <ol> <li ng-repeat="item in brr"> <input type="text" ng-model="item.name"/> </li> </ol> </div> <script src="script/app.js"></script> </body> </html>
js代码:
var directiveApp = angular.module('directiveApp',[]); directiveApp.controller('sixedCtrl',['$rootScope','$scope',function($rootScope,$scope) { $scope.arr = ['one','two','three']; $scope.brr = [{name:'jack',age:1},{name:'hana',age:4},{name:'bill',age:6}]; }]);
做用域的分层结构
在前面提到过,在AngularJS中一个scope与一个元素关联,而元素是有嵌套结构的,因此AngularJS中的scope也是有嵌套结构的(外层的做用域实例成为了内层做用域的原型),下面用一个例子来讲明这种嵌套结构。
HTML代码:
<!DOCTYPE html> <html lang="en" ng-app="scopeLevel"> <head> <meta charset="UTF-8"> <title>Title</title> <style type="text/css"> .red-border{ border:1px solid red; margin-bottom: 10px; padding:20px 20px; } </style> <script src="js/angular.js"></script> </head> <body> <div class="red-border" ng-controller="fatherCtrl"> <h3>I am {{name}}</h3> <div class="red-border" ng-controller="childCtrl"> <h3>I am {{name}}</h3> <ol> <li class="red-border" ng-repeat="item in arr">I am {{item.name}}</li> </ol> </div> </div> <div class="red-border" ng-controller="brotherCtrl"> <h3>I am {{name}}</h3> </div> <script src="script/scope-1.js"></script> </body> </html>
js代码:
var scopeLevel = angular.module('scopeLevel',[]); scopeLevel.controller('fatherCtrl',['$rootScope','$scope',function($rootScope,$scope) { $scope.name = 'Father'; }]); scopeLevel.controller('childCtrl',['$rootScope','$scope',function($rootScope,$scope) { $scope.arr = [{name:'one'},{name:'two'},{name:'three'}]; $scope.name = 'Child'; }]); scopeLevel.controller('brotherCtrl',['$rootScope','$scope',function($rootScope,$scope) { $scope.name = 'brother'; }]);
下图是例子中做用域的嵌套结构示意图:
下图是例子中做用域与HTML元素的关联状况示意图:
做用域能够像DOM节点同样,进行事件的传播。主要有两个方法:
上述两个方法发布的事件都是经过做用域 的 $on 方法进行监听的。
下面是例子:
例子的目录结构:
index.html文件
<!DOCTYPE html> <html lang="en" ng-app="scopeEventApp"> <head> <meta charset="UTF-8"> <script src="../js/angular.js"></script> <script src="../js/angular-ui-router.js"></script> <title>基于做用域的事件传递</title> </head> <body> <a ui-sref="viewone">视图-1</a> <a ui-sref="viewtwo">视图-2</a> <button ng-click="broadcastEvent()" type="button" value="click">click to broadcast</button> <div ui-view></div> <script src="js/controller.js"></script> </body> </html>
view_one.html文件
<h1>this is {{name}}</h1> <a ui-sref="viewone.child">child view</a> <button ng-click="emitEvent()" type="button" value="click me">click</button> <div ui-view></div>
view_one_child.html文件
<h1>this is {{$parent.name}}' child.</h1> <button ng-click="childEmitEvent()" type="button" value="click me">child click</button>
view_two.html文件
<h1>this is {{name}}</h1> <button ng-click="emitEvent()" type="button" value="click me">click</button>
controller.js文件
1 var scopeEventApp = angular.module('scopeEventApp',['ui.router']); 2 //订阅/发布服务 3 //不一样控制器中使用同一个服务,其实使用的是同一个对象 4 //服务中的代码是从dropzone插件中抠出来的,稍微修改了一下 5 scopeEventApp.factory('Emitter',function() { 6 var eventMap = {}; 7 var Emitter = {}; 8 Emitter.__slice = [].slice; 9 Emitter.__hasProp = {}.hasOwnProperty; 10 //事件绑定函数 11 Emitter.on = function(event, fn) { 12 eventMap = eventMap || {}; 13 if (!eventMap[event]) { 14 eventMap[event] = []; 15 } 16 eventMap[event].push(fn); 17 //return this; 18 }; 19 //触发事件的函数 20 Emitter.emit = function() { 21 var args, callback, callbacks, event, _i, _len; 22 //emit函数的第一个参数是事件的名字,保存在event变量中 23 event = arguments[0]; 24 //若是arguments.length>=2, 25 // 则args=__slice.call(arguments, 1);不然args=[] 26 args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 27 28 eventMap = eventMap || {}; 29 //如今callbacks是一个数组, 30 // 数组中的每一项是事件处理程序(本质上是一个函数) 31 callbacks = eventMap[event]; 32 33 if (callbacks) { 34 //将callbacks中的每个事件处理程序都调用一次 35 for (_i = 0, _len = callbacks.length; _i < _len; _i++) { 36 callback = callbacks[_i]; 37 callback.apply(this, args); 38 } 39 } 40 //return this; 41 }; 42 //解绑事件的函数 43 Emitter.off = function(event, fn) { 44 var callback, callbacks, i, _i, _len; 45 //若是没有_callbacks属性或者没有传参数, 46 // 则添加_callbacks属性,初始化为一个空对象 47 if (!eventMap || arguments.length === 0) { 48 eventMap = {}; 49 return; 50 } 51 //若是有_callbacks属性且传了至少一个参数, 52 // 那么callbacks的值为对应事件名字下的全部事件处理程序的集合 53 //注意callbacks变量的值多是undefined 54 callbacks = eventMap[event]; 55 //若是callbacks的值是undefined,那么返回该实例对象 56 if (!callbacks) { 57 return; 58 } 59 //若是只传了一个参数,即事件名字, 60 // 那么删除_callbacks对象中对应的键,并返回该实例对象 61 if (arguments.length === 1) { 62 delete eventMap[event]; 63 return; 64 } 65 //若是传入了两个参数,第二个参数是该事件中的一个事件处理程序的名字, 66 // 那么就将该事件处理程序从该事件对应的事件处理程序集合中的删除 67 for (i = _i = 0, _len = callbacks.length; _i < _len; i = ++_i) { 68 callback = callbacks[i]; 69 if (callback === fn) { 70 callbacks.splice(i, 1); 71 break; 72 } 73 } 74 //return this; 75 }; 76 return Emitter; 77 78 }); 79 scopeEventApp.run(function($rootScope,Emitter) { 80 $rootScope.name = 'rootScope'; 81 //向子级做用域广播事件 82 $rootScope.broadcastEvent = function() { 83 console.log($rootScope.$broadcast); 84 $rootScope.$broadcast('rootEvent',{"name":"$rootScope"}); 85 }; 86 //监听视图1发射的事件 87 $rootScope.$on("viewonetest",function(e) { 88 console.log(e); 89 alert(e.name); 90 }); 91 //监听视图2发射的事件 92 $rootScope.$on('viewonechildtest',function(e) { 93 console.log(e); 94 alert(e.name + ' ' + $rootScope.name); 95 }); 96 }); 97 //路由 98 scopeEventApp.config(function($stateProvider) { 99 $stateProvider.state('viewone',{ 100 url:'/viewone', 101 templateUrl:'tpls/view_one.html', 102 controller:'viewOneCtrl' 103 }); 104 $stateProvider.state('viewone.child',{ 105 views:{ 106 "@viewone":{ 107 url:'/viewone.child', 108 templateUrl:'tpls/view_one_child.html', 109 controller:'viewOneChildCtrl' 110 } 111 } 112 }); 113 $stateProvider.state('viewtwo',{ 114 url:'/viewtwo', 115 templateUrl:'tpls/view_two.html', 116 controller:'viewTwoCtrl' 117 }); 118 }); 119 //控制器 120 scopeEventApp.controller('viewOneCtrl', function($scope,Emitter) { 121 $scope.name = "view one"; 122 //监听父级做用域广播的事件 123 $scope.$on('rootEvent',function(e) { 124 console.log(e); 125 alert(e.name + ' --- ' + $scope.name + '监听'); 126 }); 127 //向上发射事件 128 $scope.emitEvent = function($event) { 129 $scope.$emit('viewonetest',{name:'viewOne'}); 130 Emitter.emit('test'); 131 console.log(Emitter.name); 132 }; 133 //监听子视图发射的事件 134 $scope.$on('viewonechildtest',function(e) { 135 console.log(e); 136 alert(e.name + ' --- ' + $scope.name); 137 //e.stopPropagation();//阻止事件继续向上级做用域传播 138 }); 139 }); 140 scopeEventApp.controller('viewTwoCtrl', function($scope,Emitter) { 141 $scope.name = "view two"; 142 //监听父级做用域广播的事件 143 $scope.$on('rootEvent',function(e) { 144 console.log(e); 145 alert(e.name + ' --- ' + $scope.name + '监听'); 146 }); 147 //监听视图1的子视图发射的事件---其实是监听不到的 148 $scope.$on('viewonechildtest',function(e) { 149 console.log(e); 150 alert(e.name + ' --- ' + $scope.name); 151 }); 152 //订阅/发布服务的测试 153 $scope.emitEvent = function($event) { 154 Emitter.emit('test'); 155 }; 156 }); 157 scopeEventApp.controller('viewOneChildCtrl', function($scope,Emitter) { 158 $scope.name = "view one child"; 159 //监听父级做用域广播的事件 160 $scope.$on('rootEvent',function(e) { 161 console.log(e); 162 alert(e.name + ' --- ' + $scope.name + '监听'); 163 }); 164 //视图1的子视图向上发射事件 165 $scope.childEmitEvent = function() { 166 $scope.$emit('viewonechildtest',{name:'viewOneChild'}); 167 }; 168 //在视图1的子视图的控制器中用订阅/发布服务绑定一个事件 169 Emitter.name = 'lonely'; 170 Emitter.on('test',function() { 171 console.log('this is a test'); 172 }); 173 });
例子中事件传播的示意图:
使用$broadcast和$emit发布事件时,传入这两个方法的第一个参数是要发布的事件的名字,第二个是一个对象,是要随事件发布的数据,在使用$on方法监听事件的时候经过事件处理程序的事件对象能够访问到随事件一块儿发布的数据。
经过做用域的$on方法能够监听在做用域上传播的事件,那么要怎样阻止事件在做用域上的传播呢?
要注意的是,只有$emit发出的事件是能够被停止的,$broadcast发出的不能够。
若是要阻止$emit发布的事件的继续传播,直接调用事件对象的stopPropagation()方法便可。
代码:
$scope.$on('eventName',function(e) { e.stopPropagation(); });
对于$broadcast发布的事件,没有直接阻止其传播的方法可供调用,不过只要不去监听,就能够无视其发布的事件。
下面是在其余博主的文章中介绍的另外一种方法,不过本质上并无阻止事件的传播,因此最好的方法仍是不去监听就好。
代码以下:
上级做用域:
$scope.$on('rootEvent',function(e) { e.preventDefault(); });
下级做用域:
$scope.$on('rootEvent',function(e) { if (e.defaultPrevented) { return; } });
最后要说的一点是,在例子中有一个关于订阅/发布模式的服务Emitter,在例子中的全部控制器中都用了这个服务。在视图1的子视图中用Emitter服务发布了test事件,并给Emitter添加了一个name属性,而后在视图1和视图2 以及根视图上都经过点击事件触发这个test事件并输出Emitter的name属性,从结果来看,不一样控制器引用同一个服务时,实际上他们引用的是同一个对象。
以上是关于AngularJS做用域的基本知识。
参考文章:
2. 何为做用域(Scope)