angular之scope详解

时间:2022-01-11 13:56:48

AngularJS的一些指令会创建子作用域,而子作用域会继承自父作用域,大致可分为以下3种

1、创建子作用域并继承父作用域的指令

  • ng-repeat
  • ng-include
  • ng-switch
  • ng-controller
  • directive(scope:true)
  • directive(transclude:true)
2、创建子作用域但不继承父作用域
  • directive(scope:{...})
3、不创建作用域
  • directive(scope:false)
AngularJS的作用域继承是从上到下继承的,而且这种继承机制是和javascript继承一样的。如果子作用域继承了父作用域的一个基本类型的属性,那么子作用域修改这个基本类型属性将不会同步到父作用域,而是在子作用域中添加一个该属性一样名称的属性,这个属性会覆盖父类的同名属性;如果子作用域继承了父作用域的一个属性,这个属性的值是一个对象,那么子类在修改这个对象的时候就会按照原型链的方法去寻找这个属性,修改子类的该对象的值也会同步到父类,这是javascript一样的,因为继承的是对象的引用。
下面这个例子列举该种情况
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
parent-base:<input type="text" ng-model="base" />
parent-obj:<input type="text" ng-model="obj.name">
<div ng-controller="child">
child-base:<input type="text" ng-model="base" />
child-obj:<input type="text" ng-model="obj.name"/>
</div>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function(){})
.controller("child", function(){});
</script>
</html>

在这个例子中,如果绑定的是对象的值,则父、子作用域可以相互影响,而如果绑定的是基本类型的值,则一旦改变child-base输入框的值,则就会在child的scope创建一个base属性,这个属性会覆盖parent的base,则父、子就不会相互影响了。为避免这种情况,强烈推荐使用.的对象形式绑定值。


ng-include和ng-switch和上面的ng-controller差不多,ng-repeat和这3个指令不同,它会对生成的每一项都创建一个子作用域,且这些子作用域都继承自一个父作用域。
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
<div>
arr:{{arr[0]}}
</div>
<ul ng-repeat = "age in arr">
<li>
<input ng-model="age">
</li>
</ul>
<div>
obj-arr:{{obj[0].age}}
</div>
<ul ng-repeat = "age in obj">
<li>
<input ng-model="age.age">
</li>
</ul>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function($scope){
$scope.arr = [1,2];
$scope.obj = [{age:1},{age:2}];
}); </script>
</html>

运行代码之后可以知道,ng-repeat会为每一项创建一个子作用域,它用age来遍历数组,然后子作用域会创建一个用age做属性名的属性,如果age是基本类型,则改变age不会同步到父作用域,如果是对象,则父作用域也会跟着改变。


下面是directive中scope设置不同值时的情况。
当scope=false时,这时自定义没有产生新的作用域,仍旧使用controller的作用域,从运行结果可知,虽然我们设置的name是一个基本类型,但是当输入框中的值改变时,显示的值也会跟着变化,这是因为它们的都是引用同一个scope。
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
<div ng-bind="name"></div>
<div my-name></div>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function($scope){
$scope.name = "Jhon";
}).directive("myName", function(){
return {
restrict:"A",
scope:false,
template:'<input type="text" ng-model="name"/>'
}
});
</script>
</html>

当scope=true时,这个时候会产生新的作用域,并且该子作用域继承自父作用域,从下面实例,我们仍然改变输入框的值,但是显示的值并没有改变,这和上诉内置指令的原理是一样的。

<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
<div ng-bind="name"></div>
<div my-name></div>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function($scope){
$scope.name = "Jhon";
}).directive("myName", function(){
return {
restrict:"A",
scope:true,
template:'<input type="text" ng-model="name"/>'
}
});
</script>
</html>

当scope={...}时,这时会产生一个独立的作用域,该作用域独立于任何作用域之外,不继承任何原型。但是可以通过@,=,&来和父作用域通信。

<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
<div ng-bind="name"></div>
<div my-name show-name="name"></div>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function($scope){
$scope.name = "Jhon";
}).directive("myName", function(){
return {
restrict:"A",
scope:{
title:'=showName'
},
template:'<input type="text" ng-model="title"/>'
}
});
</script>
</html>

注意:这里虽然我们使用了基本类型,但是父作用域的值还是会随着输入框的改变而改变,这与其它的继承不同。


虽然独立作用域没有继承任何原型,但我们还是能够使用$parent.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
<div ng-bind="name"></div>
<div my-name></div>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function($scope){
$scope.name = "Jhon";
}).directive("myName", function(){
return {
restrict:"A",
scope:{ },
template:'<input type="text" ng-model="$parent.name"/>'
}
});
</script>
</html>

当transclude=true时,transclude会创建自己的作用域,这个作用域继承了自定义指令外的作用域,所以下例输出mery,Jhon。

<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>AngularJS</title>
</head>
<body>
<div ng-controller="parent">
<my-name>
<span>{{name}}</span>
</my-name>
</div>
</body>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript">
var app = angular.module("myApp", []);
app.controller("parent", function($scope){
$scope.name = "Jhon";
}).directive("myName", function(){
return {
restrict:"EA",
transclude:true,
scope:{ },
template:'<div>{{name}}</div><div ng-transclude></div>',
link: function(scope){
scope.name = "mery";
}
}
});
</script>
</html>