剖析AngularJS作用域

一、概要

在AngularJS中,子作用域(child scope)基本上都要继承自父作用域(parent scope)。

但,事无绝对,也有特例,那就是指令中scope设置项为对象时,即scope:{…},这将会让指令创建一个并不继承自父作用域的子作用域,我们称之为隔离作用域(isolated scope)。

指令中的scope一共可以有三个值,下面我们再来温习下:


指令之scope


scope: false


默认值,指令不会新建一个作用域,使用父级作用域。


scope: true


指令会创建一个新的子作用域,原型继承于父级作用域。


scope: {…}


指令会新建一个隔离作用域,不会原型继承父作用域。

那么,理解AngularJS中作用域继承有什么用呢?

原因之一就是,有利于我们使用“双向绑定”(也就是在form表单元素中绑定ng-model),例如,在初学AngularJS时,我们常会遇到“双向绑定”不起作用的时候,如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myApp">
        parent:<input type="text" ng-model="name"/>
        <div ng-controller="TestCtrl">
            child: <input type="text" ng-model="name"/>
        </div>
        <script>
            var app = angular.module(‘myApp‘, []);
            app.controller(‘TestCtrl‘, function(){});
        </script>
    </body>
</html>

 执行上述代码,结果如下:

其实AngularJS的作用域继承与JavaScript的原型继承是一样的逻辑,固,如果想要上述代码实现双向绑定,我们可以利用ng-model绑定对象属性,来达到目的,如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myApp">
        parent:<input type="text" ng-model="obj.name"/>
        <div ng-controller="TestCtrl">
            child: <input type="text" ng-model="obj.name"/>
        </div>
        <script>
            var app = angular.module(‘myApp‘, []);
            app.run(function($rootScope){
                $rootScope.obj = {};
            });
            app.controller(‘TestCtrl‘, function(){});
        </script>
    </body>
</html>

执行上述代码,结果如下:

二、JavaScript原型继承

上面已经提到了AngularJS的作用域继承与JavaScript的原型继承是一样儿一样儿的,所以,我们首先来初步温习下JavaScript的原型继承。

假设,我们有父作用域(ParentScope),且其中包含了属性aString、aNumber、anArray、anObject 以及aFunction。

好了,如果现在有一子作用域(ChildScope)继承于这个父作用域(ParentScope),如下所示:

当我们通过ChildScope想访问一个属性时,JavaScript内部是如何为我们查找的呢?

答案:

首先JavaScript会第一时间在当前作用域(如这里的ChildScope)中查找是否有这一属性,如果在当前作用域中没有找到,

那么JavaScript就会沿着原型这条链(如这里的:ChildScope-->ParentScope-->RootScope),一直找下去,倘若在某一父作用域中找到,就返回这个属性值并停止原型链查找;

倘若一直找到根作用域(如这里的RootScope)都没有找到,则返undefined。

故而,下面这些表达式,结果都为true:

childScope.aString === ‘parent string‘ //true

childScope.anArray[1] === 20  //true

childScope.anObject.property1 === ‘parent prop1‘  //true

childScope.aFunction() === ‘parent output‘  //true

好了,假如,我们这么做呢:

childScope.aString = ‘child string‘

那么只会在childScope中新建一个值为’child string’的aString属性。在这之后,倘若我们还想通过childScope访问parentScope中aString属性时,就束手无策了。

因为childScope已经有了一个aString属性。理解这一点是非常重要的,在我们讨论ng-repeat 和ng-include之前。

接下来,我们再这么做呢:

childScope.anArray[1] = ‘22‘

childScope.anObject.property1 = ‘child prop1‘

这样会沿着原型链查找的,并改变属性中的值。

为什么呢?

原因就是我们这次赋值的是对象中的属性。

好了,接下来,我们再这么做:

childScope.anArray = [100, 555]

childScope.anObject = {name: ‘Mark‘, country: ‘USA‘}

这样做的效果,如同上面childScope.aString = ‘child string’一样,不会启动原型链查找。

总结:

1、如果我们读取子作用域的属性时,且该子作用域有这个属性,则不会启动原型链查找;

2、如果我们赋值子作用域的属性时,依然不会启动原型链查找。

1、If we read childScope.propertyX, and childScope has propertyX, then the prototype chain is not consulted.
2、If we set childScope.propertyX, the prototype chain is not consulted.

EnglishExpression

通过上面的总结,如果我们想让childScope在已有自己的anArray属性后,仍然访问parentScope中的anArray值呢?

哈哈,删除childScope中的anArray属性嘛,如下:

delete childScope.anArray

childScope.anArray[1] === 22 //true

三、Angular作用域继承

在前面“概要”部分已经说到Angular中子作用域基本上都继承自父作用域,但也有例外(scope:{…}指令),但并没有具体说明哪些指令会创建子作用域等,现在归类如下:


创建子作用域,且继承自父作用域


1、  ng-repeat

2、  ng-include

3、  ng-switch

4、  ng-controller

5、  directive (scope: true)

6、  directive(transclude: true)


创建子作用域,但并不继承自父作用域


directive(scope: {…})


不创建作用域


directive(scoep: false)

下面分别看看:

--ng-include--

假设我们有一控制器,内容如下:

module.controller(‘parentCtrl‘, function($scope){
    $scope.myPrimitive = 50;
    $scope.myObject = {aNumber: 11};
});

有HTML代码如下:

<div ng-controller="parentCtrl">
    parent-myPrimitive:<input type="text" ng-model="myPrimitive"/><br/>
    parent-obj.aNumber:<input type="text" ng-model="myObject.aNumber"/><br/>
    <script type="text/ng-template" id="/tpl1.html">
        includ-myPrimitive:<input ng-model = "myPrimitive"/>
    </script>
    <div ng-include src="‘/tpl1.html‘"></div>
    <script type="text/ng-template" id="/tpl2.html">
        includ-obj.aNumber:<input ng-model="myObject.aNumber"/>
    </script>
    <div ng-include src="‘/tpl2.html‘"></div>
</div>

代码稍长,请自行打开

因为每个ng-include指令,都会创建一个新的作用域,且继承于父作用域。固,代码中的关系图如下:

在chrome(需加--disable-web-security)下,执行上述代码后,得下:

从执行结果看,符合上面的关系图。

好了,倘若我们在第三个input框中,敲入字符(如77)后,子作用域会创建一个自己的myPrimitive属性,从而阻止原型链查找。

如下所示:

倘若,我们在第四个input框中,输入字符(如99)呢?子作用域会沿着原型链查找并修改,因为在这个input框中,我们绑定的是对象属性。

如下图所示:

如果我们想让第三个input框达到第四个input框的效果,并且不使用对象属性的方法,那么我们可以利用$parent来达到目的。

修改第三个input框的HTML代码:

includ-myPrimitive:<input ng-model="$parent.myPrimitive"/>

好了,这个时候,我们再在第三个input框中输入22时,就会沿着原型链查找了,达到与第四个input框一样的效果(因为$parent是为了让子作用域访问父作用域设置的属性)。

除开$parent,还有$$childHead和$$childTail都是为了子作用和父作用域通信服务的。注意,是所有子作用域哦,所以包括了scope:{…}指令的情况。

--ng-switch--

ng-switch会创建子作用域,效果与上面所述的ng-include一样。倘若,我们想要实现子作用域与父作用域实现双向绑定,就使用$parent或者对象属性。

Demo如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myModule">
        <div ng-controller="parentCtrl">
            <input type="text" ng-model="obj.something"/>
            <div ng-switch="name">
                <div ng-switch-when="Monkey">
                    <h1>This is Monkey</h1>
                    <input type="text" ng-model="obj.something"/>
                </div>
                <div ng-switch-default>
                    <h1>This is Default</h1>
                </div>
            </div>
        </div>
        <script>
            var module = angular.module(‘myModule‘, []);
            module.controller(‘parentCtrl‘, function($scope){
                $scope.obj = {};
                $scope.name = "Monkey";
            });
        </script>
    </body>
</html>

代码稍长,请自行打开

执行上述代码,效果如下:

--ng-repeat--

ng-repeat也会创建子作用域,不过与上面讲述的ng-include、ng-switch不同的是,ng-repeat会为每个遍历的元素创建子作用域,且继承自同一父作用域。

好了,下面我们来具体看看,假如,现在我们有一控制器,如下:

module.controller(‘parentCtrl‘, function($scope){
    $scope.myArrayOfPrimitives = [11, 22];
    $scope.myArrayOfObjects = [{num: 101}, {num: 202}];
});

HTML代码如下:

<div ng-controller="parentCtrl">
    <ul>
        <li ng-repeat="num in myArrayOfPrimitives">
            <input ng-model="num">
        </li>
    </ul>
    <ul>
        <li ng-repeat="obj in myArrayOfObjects">
            <input ng-model="obj.num">
        </li>
    </ul>
</div>

因为,ng-repeat会为每个元素都创建一个子作用域,如上面代码中<li ng-repeat=”num in myArrayOfPrimitives”>,ng-repeat指令会用num,去遍历myArrayOfPrimitives:[11, 22]中的数据,即创建两个继承自父作用域的子作用域。且,在子作用域中会创建自己的属性num,因此,倘若子作用域中的num值变动,肯定不会改变父作用域中myArrayOfPrimitives的相应数据咯。

然而,HTML代码中第二个出现ng-repeat的地方<li ng-repeat=”obj in myArrayOfObjects”>,当子作用域变动时,会影响到父作用域中对应的元素。因为数组myArrayOfObjects:[{…}, {…}]中的元素为对象,固而,遍历myArrayOfObjects中元素时,子作用域赋值的是对象,引用类型嘛,所以子作用域中变动对象属性时,肯定会影响父作用域的相关值咯。

--ng-controller--

ng-controller指令与ng-include、ng-switch一样,即创建子作用域,且继承自父作用域。

--directives--

详情见“初探指令

需要注意的是,scope为对象的指令(scope:{…}),虽然该类指令的作用域为隔离作用域,但是,它任然可以通过$parent访问父作用域。

Isolate scope‘s __proto__ references Object. Isolate scope‘s $parent references the parent scope, so although it is isolated and doesn‘t inherit prototypically from the parent scope, it is still a child scope. 

EnglishExpression

Demo如下:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
        <script src="angular.js"></script>
    </head>
    <body ng-app="myApp">
        <div ng-controller="TestCtrl">
            <input type="text" ng-model="name"/>
            <test></test>
        </div>
        <script>
            var app = angular.module(‘myApp‘, []);
            app.controller(‘TestCtrl‘, function($scope){
                $scope.name = ‘Monkey‘;
            });
            app.directive(‘test‘, function(){
                return {
                    restrict: ‘E‘,
                    scope: {},
                    controller: function($scope){
                        $scope.name = $scope.$parent.name;
                    },
                    template: ‘<input type="text" ng-model="$parent.name"/>‘
                };
            });
        </script>
    </body>
</html>

代码稍长,请自行打开

执行上述代码,操作如下:

四、总结

有四种类型的子作用域:

1、要继承于父作用域—ng-include, ng-switch, ng-controller, directive(scope: true).

2、要继承于父作用域,但会为每个元素创建一个子作用域—ng-repeat.

3、隔离作用域—directive(scope:{…}).

4、要继承于父作用域,且与任何的隔离作用的指令为兄弟关系—directive(transclude: true).

the directive creates a new "transcluded" child scope, which prototypically inherits from the parent scope. The transcluded and the isolated scope (if any) are siblings -- the $parent property of each scope references the same parent scope. When a transcluded and an isolate scope both exist, isolate scope property $$nextSibling will reference the transcluded scope. 

transclude:true

--Summary_in_English--

There are four types of scopes:

1、normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true
2、normal prototypal scope inheritance with a copy/assignment -- ng-repeat. Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.
3、isolate scope -- directive with scope: {...}. This one is not prototypal, but ‘=‘, ‘@‘, and ‘&‘ provide a mechanism to access parent scope properties, via attributes.
4、transcluded scope -- directive with transclude: true. This one is also normal prototypal scope inheritance, but it is also a sibling of any isolate scope.
For all scopes (prototypal or not), Angular always tracks a parent-child relationship (i.e., a hierarchy), via properties $parent and $$childHead and $$childTail.

Summary

五、参考

Understanding Scope

 

时间: 2024-10-15 15:11:07

剖析AngularJS作用域的相关文章

AngularJS作用域

AngularJS作用域 一.概要 在AngularJS中,子作用域(child scope)基本上都要继承自父作用域(parent scope). 但,事无绝对,也有特例,那就是指令中scope设置项为对象时,即scope:{…},这将会让指令创建一个并不继承自父作用域的子作用域,我们称之为隔离作用域(isolated scope). 指令中的scope一共可以有三个值,下面我们再来温习下: 指令之scope scope: false 默认值,指令不会新建一个作用域,使用父级作用域. scop

angularJS 作用域

<!doctype html><html ng-app="firstApp"><head> <meta charset="utf-8"> <script src="angular-1.3.0.js"></script></head><body><pre> </pre><div ng-controller="par

AngularJS 作用域与数据绑定机制

AngularJS 简介 AngularJS 是由 Google 发起的一款开源的前端 MVC 脚本框架,既适合做普通 WEB 应用也可以做 SPA(单页面应用,所有的用户操作都在一个页面中完成).与同为 MVC 框架的 Dojo 的定位不同,AngularJS 在功能上更加轻量,而相比于 jQuery,AngularJS 又帮您省去了许多机械的绑定工作.在一些对开发速度要求高,功能模块不需要太丰富的非企业级 WEB 应用上,AngularJS 是一个非常好的选择.AngularJS 最为复杂同

angularjs作用域之transclude

transclude是一个可选的参数.如果设置了,其值必须为true,它的默认值是false.嵌入有时被认为是一个高级主题,但某些情况下它与我们刚刚学习过的作用域之间会有非常好的配合.使用嵌入也会很好地扩充我们的工具集,特别是在创建可以在团队.项目.AngularJS社区之间共享的HTML代码片段时. 嵌入通常用来创建可复用的组件,典型的例子是模态对话框或导航栏.我们可以将整个模板,包括其中的指令通过嵌入全部传入一个指令中.这样做可以将任意内容和作用域传递给指令.transclude参数就是用来

AngularJS 作用域里的值复制和引用复制

对象要么是值复制要么是引用复制.字符串.数字和布尔型变量是值 复制.数组.对象和函数则是引用复制. 1.值复制: <!doctype html> <html ng-app="myApp"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.js"></script> </head

构建自己的AngularJS - 作用域和Digest(一)

作用域 第一章 作用域和Digest(一) Angular作用域是简单javascript对象,因此你可以像对其他对象一样添加属性.然而,他们也有一些额外的功能,用于观测数据结构的变化.这种观察能力是使用脏值检查在digest循环中运行来实现的.这就是我们这一章将要实现的内容. 作用域对象 Scope的创建是通过在Scope构造函数之前加入new关键字来创建的.这样会产生一个简单javascript对象.让我们先来创建一个单元测试.(测试驱动开发,先写测试案例) 对Scope创建一个测试文件te

构建自己的AngularJS - 作用域和Digest(二)

作用域 第一章 作用域和Digest(二) 放弃一个不稳定的Digest 在我们当前的实现中有一个明显的遗漏:如果发生了两个监控函数互相监控对方的变化的情况会如何?也就是,万一状态永远不能稳定呢?就像下面的测试案例展示的情况: test/scope_spec.js it("gives up on the watchers after 10 iterations", function(){ scope.counterA = 0; scope.counterB = 0; scope.$wa

创建自己的AngularJS - 作用域和Digest(五)

作用域 第一章 作用域和Digest(五) 销毁监控 当你注册一个监控,很多时候你想让它和scope一样保持活跃的状态,所以不必显示的删除他.然而,有些情况下,你需要销毁一个特定的监控,但是仍然保持作用域可操作.意思就是,我们需要给监控增加一个删除操作. Angular实现这个的方式特别聪明:Angular中的$watch函数有一个返回值.他是一个函数,但其被调用的时候,即删除了其注册的监控.如果想要能够移除一个监控,只要存储注册监控函数时返回的函数,然后当不需要监控的时候调用它即可: test

构建自己的AngularJS - 作用域和Digest(三)

作用域 第一章 作用域和Digest(三) $eval - 在当前作用域的上下文中执行代码 Angular有多种方式让你在当前作用域的上下文中执行代码.最简单的是$eval.传入一个函数当做其参数,然后将当前的作用域作为参数传给该函数,并执行它.然后它返回该函数的执行结果.$eval还有第二个可选的参数,它仅仅是被传递给将要执行的函数. 有几个单元测试展示了我们如何使用$eval: test/scope_spec.js it("execute $eval'ed function and retu