剖析AngularJS做用域

1、概要

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

但,事無絕對,也有特例,那就是指令中scope設置項爲對象時,即scope:{…},這將會讓指令建立一個並不繼承自父做用域的子做用域,咱們稱之爲隔離做用域(isolated scope)。angularjs

指令中的scope一共能夠有三個值,下面咱們再來溫習下:web

指令之scopechrome

scope: false數組

默認值,指令不會新建一個做用域,使用父級做用域。app

scope: trueide

指令會建立一個新的子做用域,原型繼承於父級做用域。spa

scope: {…}prototype

指令會新建一個隔離做用域,不會原型繼承父做用域。3d

那麼,理解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>

 執行上述代碼,結果以下:

 

該篇博客原文地址:http://www.cnblogs.com/giggle/p/5769047.html

2、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’同樣,不會啓動原型鏈查找。

 

總結:

一、若是咱們讀取子做用域的屬性時,且該子做用域有這個屬性,則不會啓動原型鏈查找;

二、若是咱們賦值子做用域的屬性時,依然不會啓動原型鏈查找。

一、If we read childScope.propertyX, and childScope has propertyX, then the prototype chain is not consulted.
二、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

3、Angular做用域繼承

在前面「概要」部分已經說到Angular中子做用域基本上都繼承自父做用域,但也有例外(scope:{…}指令),但並無具體說明哪些指令會建立子做用域等,如今歸類以下:

建立子做用域,且繼承自父做用域

一、  ng-repeat

二、  ng-include

三、  ng-switch

四、  ng-controller

五、  directive (scope: true)

六、  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>
代碼稍長,請自行打開

執行上述代碼,操做以下:

4、總結

有四種類型的子做用域:

一、要繼承於父做用域—ng-include, ng-switch, ng-controller, directive(scope: true).

二、要繼承於父做用域,但會爲每一個元素建立一個子做用域—ng-repeat.

三、隔離做用域—directive(scope:{…}).

四、要繼承於父做用域,且與任何的隔離做用的指令爲兄弟關係—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:

一、normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true
二、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.
三、isolate scope -- directive with scope: {...}. This one is not prototypal, but '=', '@', and '&' provide a mechanism to access parent scope properties, via attributes.
四、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
5、參考

Understanding Scope

相關文章
相關標籤/搜索