前言:javascript
上篇博文AngularJs之directive中说了Scope做用域是个大坑,因此拿出来做为重点总结!html
AngularJS 中,做用域是一个指向应用模型的对象,它是表达式的执行环境。做用域有层次结构,这个层次和相应的 DOM 几乎是同样的。做用域能监控表达式和传递事件。java
在 HTML 代码中,一旦一个 ng-app 指令被定义,那么一个做用域就产生了,由 ng-app 所生成的做用域比较特殊,它是一个根做用域($rootScope),它是其余全部$Scope 的最顶层。浏览器
除了用 ng-app 指令能够产生一个做用域以外,其余的指令如 ng-controller,ng-repeat 等都会产生一个或者多个做用域。此外,还能够经过 AngularJS 提供的建立做用域的工厂方法来建立一个做用域。这些做用域都拥有本身的继承上下文,而且根做用域都为$rootScope。app
在生成一个做用域以后,在编写 AngularJS 代码时,$scope 对象就表明了这个做用域的数据实体,咱们能够在$scope 内定义各类数据类型,以后能够直接在 HTML 中以 {{变量名}} 方式来让 HTML 访问到这个变量。函数
AngularJS 在建立一个做用域时,会检索上下文,若是上下文中已经存在一个做用域,那么这个新建立的做用域就会以 JavaScript 原型继承机制继承其父做用域的属性和方法。测试
一些 AngularJS 指令会建立新的子做用域,而且进行原型继承: ng-repeat、ng-include、ng-switch、ng-view、ng-controller, 用 scope: true 和 transclude: true 建立的 directive。spa
如下 HTML 中定义了三个做用域,分别是由 ng-app 指令所建立的$rootScope,parentCtrl 和 childCtrl 所建立的子做用域,这其中 childCtrl 生成的做用域又是 parentCtrl 的子做用域。设计
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args= 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { $scope.args= 'Nick DeveloperWorks for test'; }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
<input ng-model="args">
<div ng-controller="childCtrl">
<input ng-model="args">
</div>
</div>
</body>
</html>
继承做用域符合 JavaScript 的原型继承机制,这意味着若是咱们在子做用域中访问一个父做用域中定义的属性,JavaScript 首先在子做用域中寻找该属性,没找到再从原型链上的父做用域中寻找,若是还没找到会再往上一级原型链的父做用域寻找。在 AngularJS 中,做用域原型链的顶端是$rootScope,AnguarJS 将会寻找到$rootScope 为止,若是仍是找不到,则会返回 undefined。双向绑定
咱们用实例代码说明下这个机制。首先,咱们探讨下对于原型数据类型的做用域继承机制:
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
<input ng-model="args">
<div ng-controller="childCtrl">
<input ng-model="args">
</div>
</div>
</body>
</html>
测试运行结果:
第一个输入框:
虽然在 childCtrl 中没有定义具体的 args 属性,可是由于 childCtrl 的做用域继承自 parentCtrl 的做用域,所以,AngularJS 会找到父做用域中的 args 属性并设置到输入框中。并且,若是咱们在第一个输入框中改变内容,内容将会同步的反应到第二个输入框。
第二个输入框:
第二个输入框的内容今后将再也不和第一个输入框的内容保持同步。在改变第二个输入框的内容时,由于 HTML 代码中 model 明确绑定在 childCtrl 的做用域中,所以 AngularJS 会为 childCtrl 生成一个 args 原始类型属性。这样,根据 AngularJS 做用域继承原型机制,childCtrl 在本身的做用域找获得 args 这个属性,从而也再也不会去寻找 parentCtrl 的 args 属性。今后,两个输入框的内容所绑定的属性已是两份不一样的实例,所以不会再保持同步。
现将代码作以下修改,结合以上两个场景,会出现怎样的结果?
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
<input ng-model="args.content">
<div ng-controller="childCtrl">
<input ng-model="args.content">
</div>
</div>
</body>
</html>
测试结果是不管改变任何一个输入框的内容,二者的内容始终同步。
根据 AngularJS 的原型继承机制,若是 ng-model 绑定的是一个对象数据,那么 AngularJS 将不会为 childCtrl 建立一个 args 的对象,天然也不会有 args.content 属性。这样,childCtrl 做用域中将始终不会存在 args.content 属性,只能从父做用域中寻找,也便是两个输入框的的变化其实只是在改变 parentCtrl 做用域中的 args.content 属性。所以,二者的内容始终保持同步。
咱们再看一个例子,分析结果如何。
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'Nick DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'Nick DeveloperWorks for test'; }]); </script>
<body ng-app="app">
<div ng-controller="parentCtrl">
<input ng-model="args.content">
<div ng-controller="childCtrl">
<input ng-model="args.content">
</div>
</div>
</body>
</html>
测试结果是两个输入框的内容永远不会同步。子做用域有实例数据对象,则不访问父做用域。
独立做用域是 AngularJS 中一个很是特殊的做用域,它只在 directive 中出现。在对 directive 的定义中,咱们添加上一个 scope:{} 属性,就为这个 directive 建立出了一个隔离做用域。
angular.module('isolate', []).directive("isolate", function () { return { scope : {}, }; })
独立做用域最大的特色是不会原型继承其父做用域,对外界的父做用域保持相对的独立。所以,若是在定义了孤立做用域的 AngularJS directive 中想要访问其父做用域的属性,则获得的值为 undefined。代码以下:
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script type="text/javascript"> angular.module('app', []) .controller('ctrl', ['$scope', function($scope) { $scope.args = {}; }]) .directive("isolateDirective", function () { return { scope : {}, link : function($scope, $element, $attr) { console.log($scope.$args); //输出 undefined
} }; }); </script>
<body ng-app="app">
<div ng-controller="ctrl">
<div isolate-directive></div>
</div>
</body>
</html>
上面的代码中经过在 directive 中声明了 scope 属性从而建立了一个做用域,其父做用域为 ctrl 所属的做用域。可是,这个做用域是孤立的,所以,它访问不到父做用域的中的任何属性。存在这样设计机制的好处是:可以建立出一些列可复用的 directive,这些 directive 不会相互在拥有的属性值上产生串扰,也不会产生任何反作用。
在继承做用域中,咱们能够选择子做用域直接操做父做用域数据来实现父子做用域的通讯,而在独立做用域中,子做用域不能直接访问和修改父做用域的属性和值。为了可以使孤立做用域也能和外界通讯,AngularJS 提供了三种方式用来打破独立做用域“孤立”这一限制。
这是 AngularJS 独立做用域与外界父做用域进行数据通讯中最简单的一种,绑定的对象只能是父做用域中的字符串值,而且为单向只读引用,没法对父做用域中的字符串值进行修改,此外,这个字符串还必须在父做用域的 HTML 节点中以 attr(属性)的方式声明。
使用这种绑定方式时,须要在 directive 的 scope 属性中明确指定引用父做用域中的 HTML 字符串属性,不然会抛异常。示例代码以下:
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, template: '<button>{{isolates}}</button>', scope : { isolates : '@', }, link : function($scope, $element, $attr) { $scope.isolates = "DeveloperWorks";//无效
} }; }) .controller("ctrl", function ($scope) { $scope.btns = 'NICK'; }); </script>
<body ng-app="isolateScope" >
<div ng-controller="ctrl">
<button>{{btns}}</button>
<div isolate-directive isolates="{{btns}}"></div>
</div>
</body>
</html>
上面的代码,经过在 directive 中声明了 scope:{isolates:'@'} 使得 directive 拥有了父做用域中 data-isolates (isolates为自定义属性,不加data也能够,但建议加上data)这个 HTML 属性所拥有的值,这个值在控制器 ctrl 中被赋值为'nick'。因此,代码的运行结果是页面上有两个名为 nick的按钮。
咱们还注意到 link 函数中对 isolates 进行了修改,可是最终不会在运行结果中体现。这是由于 isolates 始终绑定为父做用域中的 btns 字符串,若是父做用域中的 btns 不改变,那么在孤立做用域中不管怎么修改 isolates 都不会起做用。
经过这种形式的绑定,孤立做用域将有能力访问到父做用域中的函数对象,从而可以执行父做用域中的函数来获取某些结果。这种方式的绑定跟单向绑定同样,只能以只读的方式访问父做用函数,而且这个函数的定义必须写在父做用域 HTML 中的 attr(属性)节点上。
这种方式的绑定虽然没法修改父做用域的 attr 所设定的函数对象,可是却能够经过执行函数来改变父做用域中某些属性的值,来达到一些预期的效果。示例代码以下:
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, scope : { isolates : '&', }, link : function($scope, $element, $attr) { var func = $scope.isolates(); func(); } }; }) .controller("ctrl", function ($scope) { $scope.func = function () { console.log("Nick DeveloperWorks"); } }); </script>
<body ng-app="isolateScope" >
<div ng-controller="ctrl">
<div isolate-directive data-isolates="func"></div>
</div>
</body>
</html>
这个例子中,浏览器的控制台将会打印“Nick DeveloperWorks”文字。
上面的代码中咱们在父做用域中指定了一个函数对象$scope.func,在孤立做用域中经过对 HTML 属性的绑定从而引用了 func。须要注意的是 link 函数中对 func 对象的使用方法,$scope.isolates 得到的仅仅是函数对象,而不是调用这个对象,所以咱们须要在调用完$scope.isolates 以后再调用这个函数,才能获得真正的执行结果。
双向绑定赋予 AngularJS 孤立做用域与外界最为自由的双向数据通讯功能。在双向绑定模式下,孤立做用域可以直接读写父做用域中的属性和数据。和以上两种孤立做用域定义数据绑定同样,双向绑定也必须在父做用域的 HTML 中设定属性节点来绑定。
双向绑定很是适用于一些子 directive 须要频繁和父做用域进行数据交互,而且数据比较复杂的场景。不过,因为能够自由的读写父做用域中的属性和对象,因此在一些多个 directive 共享父做用域数据的场景下须要当心使用,很容易引发数据上的混乱。
示例代码以下:
<!doctype html>
<html>
<head>
<meta charset=utf-8"/>
<title>scope nick</title>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, template: '<button>{{isolates}}</button>', scope : { isolates : '=', }, link : function($scope, $element, $attr) { $scope.isolates.name = "NICK"; } }; }) .controller("ctrl", function ($scope) { $scope.btns = { name : 'nick', dw : 'DeveloperWorks' }; }); </script>
<body ng-app="isolateScope" >
<div ng-controller="ctrl">
<button>{{btns.dw}}</button>
<button>{{btns.name}}</button>
<div isolate-directive data-isolates="btns"></div>
</div>
</body>
</html>
上面的代码运行的结果是浏览器页面上出现三个按钮,其中第一个按钮标题为“DeveloperWorks”,第二和第三个按钮的标题为“NICK”。
初始时父做用域中的$scope.btns.name为小写的“nick”,经过双向绑定,孤立做用域中将父做用域的 name改写成为大写的“NICK”而且直接生效,父做用域的值被更改。