1049

API リファレンス スコープ ページには次のように記載されています。

スコープは、親スコープから継承できます。

開発者ガイドのスコープ ページには次のように記載されています。

スコープは (プロトタイプとして) 親スコープからプロパティを継承します。

  • では、子スコープは常に親スコープからプロトタイプを継承するのでしょうか?
  • 例外はありますか?
  • 継承する場合は、常に通常の JavaScript プロトタイプ継承ですか?
4

3 に答える 3

1756

クイックアンサー
子スコープは通常、親スコープからプロトタイプ的に継承しますが、常にそうであるとは限りません。このルールの1つの例外は、次のディレクティブですscope: { ... }。これにより、プロトタイプ的に継承しない「分離」スコープが作成されます。この構成は、「再利用可能なコンポーネント」ディレクティブを作成するときによく使用されます。

ニュアンスに関しては、スコープの継承は通常簡単です...子スコープで双方向のデータバインディング(つまり、フォーム要素、ng-model)が必要になるまで。Ng-repeat、ng-switch、およびng-includeは、子スコープ内から親スコープのプリミティブ(数値、文字列、ブール値など)にバインドしようとすると、つまずく可能性があります。ほとんどの人が期待するようには機能しません。子スコープは、同じ名前の親プロパティを非表示/シャドウする独自のプロパティを取得します。あなたの回避策は

  1. モデルの親でオブジェクトを定義してから、子でそのオブジェクトのプロパティを参照します:parentObj.someProp
  2. $ parent.parentScopePropertyを使用します(常に可能であるとは限りませんが、可能な場合は1よりも簡単です)
  3. 親スコープで関数を定義し、子から呼び出す(常に可能とは限りません)

新しいAngularJS開発者は、、、、およびすべてが新しい子スコープを作成することに気付かないことが多いため、これらのディレクティブが関係している場合に問題が発生することがよくありng-repeatます。(問題の簡単な説明については、この例を参照してください。)ng-switchng-viewng-includeng-if

プリミティブに関するこの問題は、常に「。」を持つという「ベストプラクティス」に従うことで簡単に回避できます。あなたのng-modelsで–3分の価値を見てください。Miskoは、でプリミティブバインディングの問題を示していng-switchます。

'。'を持つ モデルで、プロトタイプの継承が機能していることを確認します。だから、使用する

<input type="text" ng-model="someObj.prop1">

<!--rather than
<input type="text" ng-model="prop1">`
-->


長い答え

JavaScriptのプロトタイプの継承

AngularJS wikiにも配置されています: https ://github.com/angular/angular.js/wiki/Understanding-Scopes

特にサーバー側のバックグラウンドから来ており、古典的な継承に精通している場合は、最初にプロトタイプの継承をしっかりと理解することが重要です。それでは、最初にそれを確認しましょう。

parentScopeにプロパティaString、aNumber、anArray、anObject、およびaFunctionがあるとします。childScopeが典型的にparentScopeから継承する場合、次のようになります。

プロトタイプの継承

(スペースを節約するために、anArray3つの別々の灰色のリテラルを持つ単一の青いオブジェクトではなく、3つの値を持つ単一の青いオブジェクトとしてオブジェクトを表示することに注意してください。)

子スコープからparentScopeで定義されたプロパティにアクセスしようとすると、JavaScriptは最初に子スコープを調べ、プロパティを見つけず、次に継承されたスコープを調べてプロパティを見つけます。(parentScopeでプロパティが見つからなかった場合は、プロトタイプチェーンを継続します...ルートスコープまで続きます)。したがって、これらはすべて真実です。

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

次に、これを行うとします。

childScope.aString = 'child string'

プロトタイプチェーンは参照されず、新しいaStringプロパティがchildScopeに追加されます。 この新しいプロパティは、同じ名前のparentScopeプロパティを非表示/シャドウします。 これは、以下でng-repeatとng-includeについて説明するときに非常に重要になります。

プロパティの非表示

次に、これを行うとします。

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

オブジェクト(anArrayおよびanObject)がchildScopeに見つからないため、プロトタイプチェーンが参照されます。オブジェクトはparentScopeにあり、プロパティ値は元のオブジェクトで更新されます。childScopeに新しいプロパティは追加されません。新しいオブジェクトは作成されません。(JavaScriptでは、配列と関数もオブジェクトであることに注意してください。)

プロトタイプチェーンに従う

次に、これを行うとします。

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

プロトタイプチェーンは参照されず、子スコープは、同じ名前のparentScopeオブジェクトプロパティを非表示/シャドウする2つの新しいオブジェクトプロパティを取得します。

より多くのプロパティを非表示

要点:

  • childScope.propertyXを読み取り、childScopeにpropertyXがある場合、プロトタイプチェーンは参照されません。
  • childScope.propertyXを設定した場合、プロトタイプチェーンは参照されません。

最後のシナリオ:

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

最初にchildScopeプロパティを削除し、次にプロパティに再度アクセスしようとすると、プロトタイプチェーンが参照されます。

子プロパティを削除した後


角度スコープの継承

候補者:

  • 以下は、新しいスコープを作成し、プロトタイプで継承します:ng-repeat、ng-include、ng-switch、ng-controller、directive with scope: true、directivewith transclude: true
  • 以下は、プロトタイプを継承しない新しいスコープを作成しscope: { ... }ます。これにより、代わりに「分離」スコープが作成されます。

デフォルトでは、ディレクティブは新しいスコープを作成しないことに注意してください。つまり、デフォルトはですscope: false

ng-include

コントローラに次のようなものがあるとします。

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

そして私たちのHTMLでは:

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

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

各ng-includeは、親スコープからプロトタイプを継承する新しい子スコープを生成します。

ng-子スコープを含める

最初の入力テキストボックスに「77」と入力すると、子スコープmyPrimitiveは、同じ名前の親スコーププロパティを非表示/シャドウにする新しいスコーププロパティを取得します。これはおそらくあなたが望む/期待するものではありません。

ng-プリミティブに含める

2番目の入力テキストボックスに「99」と入力しても、新しい子プロパティは作成されません。tpl2.htmlはモデルをオブジェクトプロパティにバインドするため、プロトタイプの継承は、ngModelがオブジェクトmyObjectを検索するときに開始され、親スコープで検出されます。

ng-オブジェクトに含める

モデルをプリミティブからオブジェクトに変更したくない場合は、最初のテンプレートを$parentを使用するように書き直すことができます。

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

この入力テキストボックスに「22」と入力しても、新しい子プロパティは作成されません。これで、モデルは親スコープのプロパティにバインドされます($ parentは親スコープを参照する子スコーププロパティであるため)。

ng-$parentに含める

すべてのスコープ(プロトタイプかどうか)について、Angularは常にスコーププロパティ$ parent、$$ childHead、$$ childTailを介して親子関係(つまり階層)を追跡します。私は通常、これらのスコープのプロパティを図に示していません。

フォーム要素が含まれないシナリオの場合、別の解決策は、親スコープで関数を定義してプリミティブを変更することです。次に、子が常にこの関数を呼び出すようにします。この関数は、プロトタイプの継承により子スコープで使用できます。例えば、

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

これは、この「親関数」アプローチを使用するフィドルのサンプルです。(フィドルはこの回答の一部として書かれました:https ://stackoverflow.com/a/14104318/215945 。)

https://stackoverflow.com/a/13782671/215945およびhttps://github.com/angular/angular.js/issues/1267も参照してください。

ng-switch

ng-switchスコープの継承は、ng-includeと同じように機能します。したがって、親スコープのプリミティブへの双方向データバインディングが必要な場合は、$ parentを使用するか、モデルをオブジェクトに変更してから、そのオブジェクトのプロパティにバインドします。これにより、親スコープのプロパティの子スコープの非表示/シャドウイングが回避されます。

AngularJS、switch-caseのバインドスコープも参照してください。

ng-repeat

Ng-repeatの動作は少し異なります。コントローラに次のようなものがあるとします。

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

そして私たちのHTMLでは:

<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>

アイテム/反復ごとに、ng-repeatは新しいスコープを作成します。これは通常、親スコープから継承しますが、アイテムの値を新しい子スコープの新しいプロパティに割り当てます。(新しいプロパティの名前はループ変数の名前です。)ng-repeatのAngularソースコードは実際には次のとおりです。

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

itemがプリミティブの場合(myArrayOfPrimitivesのように)、基本的に値のコピーが新しい子スコーププロパティに割り当てられます。子スコーププロパティの値を変更しても(つまり、ng-modelを使用して、したがって子スコープを使用して) 、親スコープが参照する配列は変更されnumません。したがって、上記の最初のng-repeatでは、各子スコープはnummyArrayOfPrimitives配列から独立したプロパティを取得します。

ng-プリミティブで繰り返す

このng-repeatは機能しません(あなたが望む/期待するように)。テキストボックスに入力すると、子スコープにのみ表示される灰色のボックスの値が変更されます。必要なのは、入力がmyArrayOfPrimitives配列に影響を与えることであり、子スコープのプリミティブプロパティではありません。これを実現するには、モデルをオブジェクトの配列に変更する必要があります。

したがって、アイテムがオブジェクトの場合、元のオブジェクト(コピーではない)への参照が新しい子スコーププロパティに割り当てられます。子スコーププロパティの値を変更すると(つまり、ng-modelを使用してobj.num)、親スコープが参照するオブジェクトが変更されます。したがって、上記の2番目のng-repeatでは、次のようになります。

ng-オブジェクトで繰り返す

(私は、それがどこに向かっているのかが明確になるように、1本の線を灰色に着色しました。)

これは期待どおりに機能します。テキストボックスに入力すると、灰色のボックスの値が変更され、子スコープと親スコープの両方に表示されます。

ng-model、ng-repeat、inputsの難易度および https://stackoverflow.com/a/13782671/215945も参照してください。

ng-コントローラー

ng-controllerを使用してコントローラーをネストすると、ng-includeやng-switchと同様に、通常のプロトタイプの継承が行われるため、同じ手法が適用されます。ただし、「2つのコントローラーが$ scope継承を介して情報を共有することは悪い形式と見なされます」-http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ サービスを使用してデータを共有する必要があります代わりにコントローラー。

(コントローラースコープの継承を介してデータを本当に共有したい場合は、何もする必要はありません。子スコープはすべての親スコープのプロパティにアクセスできます。「コントローラーのロード順序は、ロードまたはナビゲート時に異なります」も参照してください)

ディレクティブ

  1. default(scope: false)-ディレクティブは新しいスコープを作成しないため、ここには継承はありません。これは簡単ですが、危険でもあります。たとえば、ディレクティブは、実際には既存のプロパティを破壊しているのに、スコープ上に新しいプロパティを作成していると見なす可能性があるためです。これは、再利用可能なコンポーネントとして意図されたディレクティブを作成するのに適していません。
  2. scope: true-ディレクティブは、親スコープからプロトタイプを継承する新しい子スコープを作成します。(同じDOM要素上の)複数のディレクティブが新しい​​スコープを要求する場合、1つの新しい子スコープのみが作成されます。「通常の」プロトタイプの継承があるため、これはng-includeやng-switchに似ています。したがって、親スコーププリミティブへの双方向データバインディング、および親スコーププロパティの子スコープの非表示/シャドウイングに注意してください。
  3. scope: { ... }-ディレクティブは、新しい分離/分離スコープを作成します。プロトタイプ的には継承しません。ディレクティブが誤って親スコープを読み取ったり変更したりすることはないため、これは通常、再利用可能なコンポーネントを作成する場合の最良の選択です。ただし、このようなディレクティブでは、多くの場合、いくつかの親スコーププロパティにアクセスする必要があります。オブジェクトハッシュは、親スコープと分離スコープの間に双方向バインディング(「=」を使用)または一方向バインディング(「@」を使用)を設定するために使用されます。親スコープ式にバインドするための「&」もあります。したがって、これらはすべて、親スコープから派生したローカルスコーププロパティを作成します。属性はバインディングの設定を支援するために使用されることに注意してください。オブジェクトハッシュで親スコープのプロパティ名を参照するだけでなく、属性を使用する必要があります。たとえば、親プロパティにバインドする場合、これは機能しませんparentProp分離されたスコープ内:<div my-directive>およびscope: { localProp: '@parentProp' }。属性を使用して、ディレクティブがバインドする各親プロパティを指定する必要があります:<div my-directive the-Parent-Prop=parentProp>およびscope: { localProp: '@theParentProp' }
    スコープの__proto__参照オブジェクトを分離します。Isolateスコープの$parentは親スコープを参照するため、分離されて親スコープからプロトタイプを継承しませんが、それでも子スコープです。
    以下の図で は、ディレクティブがリンク関数でこれを実行すると仮定します。
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">分離 スコープの詳細については、http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/を参照してください。
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
    scope.someIsolateProp = "I'm isolated"
    分離スコープ
  4. transclude: true-ディレクティブは、新しい「トランスクルージョンされた」子スコープを作成します。これは、通常、親スコープから継承します。トランスクルージョンされたスコープと分離されたスコープ(存在する場合)は兄弟です。各スコープの$ parentプロパティは、同じ親スコープを参照します。トランスクルージョンスコープと分離スコープの両方が存在する場合、分離スコーププロパティ$$nextSiblingはトランスクルージョンスコープを参照します。トランスクルージョンされたスコープのニュアンスはわかりません。
    次の図では、上記と同じディレクティブに次の追加を加えたものと想定しています。transclude: true
    トランスクルージョンスコープ

このフィドルにはshowScope()、分離されたスコープとトランスクルージョンされたスコープを調べるために使用できる機能があります。フィドルのコメントの説明を参照してください。


概要

スコープには次の4つのタイプがあります。

  1. 通常のプロトタイプスコープの継承-ng-include、ng-switch、ng-controller、directive withscope: true
  2. コピー/割り当てを使用した通常のプロトタイプスコープの継承--ng-repeat。ng-repeatを繰り返すたびに、新しい子スコープが作成され、その新しい子スコープは常に新しいプロパティを取得します。
  3. スコープを分離します-ディレクティブをscope: {...}。これはプロトタイプではありませんが、「=」、「@」、および「&」は、属性を介して親スコープのプロパティにアクセスするメカニズムを提供します。
  4. トランスクルージョンされたスコープ-。を含むディレクティブtransclude: true。これも通常のプロトタイプスコープの継承ですが、分離スコープの兄弟でもあります。

すべてのスコープ(プロトタイプかどうか)について、Angularは常にプロパティ$parentと$$childHeadと$$childTailを介して、親子関係(つまり階層)を追跡します。

ダイアグラムは、 githubにある "*.dot"ファイルを使用して生成されました。TimCaswellの「オブジェクトグラフを使用したJavaScriptの学習」は、ダイアグラムにGraphVizを使用するためのインスピレーションでした。

于 2012-12-27T04:48:31.323 に答える
143

私は決してマークの答えと競合したくありませんが、 Javascript の継承とそのプロトタイプ チェーンの初心者として、最終的にすべてがクリックされた部分を強調したかっただけです。

プロパティの読み取りのみがプロトタイプ チェーンを検索し、書き込みは検索しません。だからあなたが設定するとき

myObject.prop = '123';

チェーンを調べませんが、設定すると

myObject.myThing.prop = '123';

その prop に書き込む前に myThing を検索しようとする、その書き込み操作内で微妙な読み取りが行われています。そのため、子から object.properties に書き込むと、親のオブジェクトが取得されます。

于 2014-05-16T15:22:25.543 に答える
21

@Scott Driscollの回答に、javascriptを使用したプロトタイプの継承の例を追加したいと思います。EcmaScript 5 仕様の一部である Object.create() で古典的な継承パターンを使用します。

まず、「親」オブジェクト関数を作成します

function Parent(){

}

次に、「親」オブジェクト関数にプロトタイプを追加します

 Parent.prototype = {
 primitive : 1,
 object : {
    one : 1
   }
}

「子」オブジェクト関数を作成する

function Child(){

}

子プロトタイプの割り当て (子プロトタイプを親プロトタイプから継承させる)

Child.prototype = Object.create(Parent.prototype);

適切な「子」プロトタイプ コンストラクターを割り当てる

Child.prototype.constructor = Child;

メソッド「changeProps」を子プロトタイプに追加します。これにより、子オブジェクトの「プリミティブ」プロパティ値が書き換えられ、子オブジェクトと親オブジェクトの両方の「object.one」値が変更されます。

Child.prototype.changeProps = function(){
    this.primitive = 2;
    this.object.one = 2;
};

親 (お父さん) と子 (息子) オブジェクトを開始します。

var dad = new Parent();
var son = new Child();

子 (息子) の changeProps メソッドを呼び出す

son.changeProps();

結果を確認します。

親プリミティブ プロパティは変更されませんでした

console.log(dad.primitive); /* 1 */

子プリミティブ プロパティの変更 (書き換え)

console.log(son.primitive); /* 2 */

親と子の object.one プロパティが変更されました

console.log(dad.object.one); /* 2 */
console.log(son.object.one); /* 2 */

ここでの作業例http://jsbin.com/xexurukiso/1/edit/

Object.create の詳細はこちらhttps://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create

于 2014-11-08T22:45:01.883 に答える