從 JavaScript 繼承提及, 深刻理解 Angular Scope 繼承關係

原文發自個人博客 xiaoyu2er.github.iojavascript

易企秀招聘啦!html

JavaScript Prototypal Inheritance

首先咱們先來回顧如下 javascript 中出現的原型繼承:java

function ParentScope(){
    this.aString = "parent string";
    this.aNumber = 100;
    this.anArray = [10,20,30];
    this.anObject = {
    'property1': 'parent prop1',
    'property2': 'parent prop2' 
    };
    this.aFunction = function(){ 
      return 'parent output'; 
    }
}

function ChildScope(){    
}

ChildScope.prototype = new ParentScope();

var childScope = new ChildScope();

ChildScope 原型繼承自 ParentScopegit

clipboard.png

若是咱們要在 childScope 上查詢一個定義在 parentScope 的屬性, JavaScript 會先在 childScope 上查找, 若是沒有查到, 那麼會順着原型鏈去查找. 因此如下判別式均爲 truegithub

childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'

若是咱們作以下操做:app

childScope.aString = 'child string'

原型鏈並無被訪問, 一個新的 aString 會被加入到 childScope 的屬性中去, 新的屬性會隱藏 parentScope 中的同名屬性.dom

clipboard.png

假設咱們作出以下操做:this

childScope.anArray[1] = 22
childScope.anObject.property1 = 'child prop1'

原型鏈被訪問了. 由於 anArray, anObject 沒有在 childScope 中找到.
新的賦值操做均在 parentScope 上進行. childScope 上沒有添加任何新的屬性.spa

clipboard.png

若是咱們作出以下操做prototype

childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }

clipboard.png

原型鏈沒有被訪問, childScope 會得到兩個新的屬性, 而且會隱藏 parentScope 上的同名屬性.

仔細體會上面的三次操做. 第一第三次均是對某個屬性進行賦值, 原型鏈並不會被訪問, 因爲屬性並不存在, 因此新的屬性將會被添加. 而第二次實際上是先訪問, childScope.anArray, childScope.anObject, 再對其訪問的對象的某個屬性進行復制.

總結:

  • 若是咱們讀取 childScope.propertyX, 而 childScope 擁有 propertyX, 那麼原型鏈不會被訪問

  • 若是咱們讀取 childScope.propertyX, 而 childScope 並無 propertyX, 那麼原型鏈會被訪問.

  • 若是對 childScope.propertyX 進行賦值, 那麼原型鏈並不會被訪問.

最後咱們再來看一種狀況:

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

clipboard.png

咱們顯示刪除了 childScope 的一個屬性, 接着試圖讀取這個屬性, 因爲 childScope 並無了這個屬性, 因此原型鏈會被訪問.

Angular Scope Inheritance

接着咱們來看看 Angular 中的 scope 繼承

如下指令會建立新的 scope, 而且會在原型上繼承 父scope (即$scope.$parent, 下文兩個詞互爲同義詞):

  • ng-repeat

  • ng-switch

  • ng-view

  • ng-controller

  • 帶有 scope: true 的指令

  • 帶有 transclude: true 的指令

如下指令建立新的指令, 且在原型上 不繼承 父scope:

  • 帶有 scope: { ... } 的指令, 這會建立一個 獨立的scope (isolate scope)

注意: 默認指令並不會建立 scope, 默認是 scope: false, 一般稱之爲 共享scope.

讓咱們來看幾個例子:

ng-include

JS:

$scope.myPrimitive = 50;
$scope.myObject    = {aNumber: 11};

HTML:

<p>{{ myPrimitive }}</p>
<p>{{ myObject.aNumber }}</p>

<script type="text/ng-template" id="/tpl1.html">
    <input type="number" ng-model="myPrimitive">
</script>
<div ng-include src="'/tpl1.html'"></div>

<script type="text/ng-template" id="/tpl2.html">
    <input type="number" ng-model="myObject.aNumber">
</script>
<div ng-include src="'/tpl2.html'"></div>

每個 ng-include 都會建立一個 子scope, 並在原型上繼承 父 scope

clipboard.png

向第一個 input 輸入數字, 一個新的屬性 myPrimitive 將會被建立, 同時隱藏 父 scope 的 myPrimitive;

圖片描述
向第二個 input 輸入數字, 子 scope 並不會建立一個新的屬性, 這時候原型繼承發揮了做用.

clipboard.png

第一種狀況極可能不是咱們期待的結果, 因此能夠顯式的調用 $parent 來解決這個問題.

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

向第一個 input 鍵入數字, 這時候就不會產生新的屬性了. $parent 指向了 父scope. 可是 $parent 和 原型上的繼承並不必定相等. 稍後咱們會看到一個例子.

clipboard.png

對於全部的 scope, 不管是共享的(scope: false), 繼承的(scope: true), 仍是孤立的(scope: { ... }), Angular 都會創建一個 父-子 的層級關係, 這個層級關係是根據 dom 結構的層級關係決定的, 能夠經過

$parent
$$childHead
$$childTail

來訪問.

爲了不剛纔的例子出現的子 scope 建立新屬性狀況的發聲, 除了使用 $scope, 還可使用調用原型鏈上的方法.

// in the parent scope
$scope.setMyPrimitive = function(value) {
    $scope.myPrimitive = value;
}

ng-switch ng-view

ng-switch, ng-view 與 ng-include 狀況相似, 不贅述.

ng-repeat

ng-repeat 有一點特殊.

JS:

$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]

HTML:

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

對於每一次迭代, ng-repeat 都會建立一個 子scope, 並在原型上繼承 父scope, 可是他還會將 父scope 上的屬性賦值到 子scope 上. 新的屬性名就是 ng-repeat="** in parentScope.property" 中的 **.
源碼中的 ng-repeat 是這樣的:

childScope = scope.$new(); // child scope prototypically inherits from parent scope ...     
childScope[valueIdent] = value; // creates a new childScope property

若是 ** 是 primitive, 那麼一份 copy 會被賦值到新的屬性上. 修改 子scope 上的新屬性天然不會修改 父 scope 上的屬性.

clipboard.png

若是 ** 是個 object, 那麼一個 reference 會被賦值到新的 子scope 屬性上. 修改這個屬性, 就是修改 父scope 對應的屬性.

clipboard.png

ng-controller

ng-controller 也是會建立新的 子scope, 同時原型繼承 父scope. 如同 ng-include, ng-switch, ng-view.
可是, 使用 $scope 來共享數據被認爲是一種很差的操做. 由於原型鏈但是會一直向上追溯的.
若是想要共享數據, 最好使用 service.

Angular Directives

咱們來總結如下指令中的 scope:

  1. scope: false(默認的), 指令不會建立新的 scope, 沒有繼承關係. 與 $parent 共享 $scope.

  2. scope: true, 指令會建立一個 子scope, 並在原型上繼承 $parent. 若是在一個 DOM 上有多個指令想要建立新的 scope, 會報錯.

  3. scope: { ... }, 指令會建立一個 孤立的scope. 這在建立可重用的組件時是最好的選擇. 可是, 即便如此, 指令仍是但願讀取 $parent 的數據. 這個時候可使用以下符號得到:

    • scope: { **: "="} 與 $parent 創建雙向綁定.

    • scope: { **: "@"} 與 $parent 創建單向綁定.

    • scope: { **: "&"} 綁定 $parent 的表達式.
      想要得到相應的屬性, 必須經過指令上的屬性得到

    • HTML: <div my-directive the-Parent-Prop=parentProp>

    • JS: scope: { localProp: '@theParentProp' }
      假設:

  • HTML: <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">

  • JS: scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }

  • 指令在 link 期間: scope.someIsolateProp = "I'm isolated"
    其中的關係如圖:

clipboard.png

  1. transclude: true, 指令建立了一個 "transcluded" 的子scope, 在原型上繼承其 父scope. 若是上述例子同時具備transclude: true. 那麼這個 "transcluded" scope, 和 "islolated" scope 是姊妹關係. 他們的 $parent 指向同一個 scope. 且 isolate scope 的 $$nextSibling 就是這個 "transcluded scope". 下圖反應了他們之間的關係:
    clipboard.png

Reference

相關文章
相關標籤/搜索