做用域是控制器與視圖,指令與視圖之間的粘合劑。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)