API リファレンス スコープ ページには次のように記載されています。
スコープは、親スコープから継承できます。
開発者ガイドのスコープ ページには次のように記載されています。
スコープは (プロトタイプとして) 親スコープからプロパティを継承します。
- では、子スコープは常に親スコープからプロトタイプを継承するのでしょうか?
- 例外はありますか?
- 継承する場合は、常に通常の JavaScript プロトタイプ継承ですか?
API リファレンス スコープ ページには次のように記載されています。
スコープは、親スコープから継承できます。
開発者ガイドのスコープ ページには次のように記載されています。
スコープは (プロトタイプとして) 親スコープからプロパティを継承します。
クイックアンサー:
子スコープは通常、親スコープからプロトタイプ的に継承しますが、常にそうであるとは限りません。このルールの1つの例外は、次のディレクティブですscope: { ... }
。これにより、プロトタイプ的に継承しない「分離」スコープが作成されます。この構成は、「再利用可能なコンポーネント」ディレクティブを作成するときによく使用されます。
ニュアンスに関しては、スコープの継承は通常簡単です...子スコープで双方向のデータバインディング(つまり、フォーム要素、ng-model)が必要になるまで。Ng-repeat、ng-switch、およびng-includeは、子スコープ内から親スコープのプリミティブ(数値、文字列、ブール値など)にバインドしようとすると、つまずく可能性があります。ほとんどの人が期待するようには機能しません。子スコープは、同じ名前の親プロパティを非表示/シャドウする独自のプロパティを取得します。あなたの回避策は
新しいAngularJS開発者は、、、、およびすべてが新しい子スコープを作成することに気付かないことが多いため、これらのディレクティブが関係している場合に問題が発生することがよくありng-repeat
ます。(問題の簡単な説明については、この例を参照してください。)ng-switch
ng-view
ng-include
ng-if
プリミティブに関するこの問題は、常に「。」を持つという「ベストプラクティス」に従うことで簡単に回避できます。あなたのng-modelsで–3分の価値を見てください。Miskoは、でプリミティブバインディングの問題を示していng-switch
ます。
'。'を持つ モデルで、プロトタイプの継承が機能していることを確認します。だから、使用する
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
AngularJS wikiにも配置されています: https ://github.com/angular/angular.js/wiki/Understanding-Scopes
特にサーバー側のバックグラウンドから来ており、古典的な継承に精通している場合は、最初にプロトタイプの継承をしっかりと理解することが重要です。それでは、最初にそれを確認しましょう。
parentScopeにプロパティaString、aNumber、anArray、anObject、およびaFunctionがあるとします。childScopeが典型的にparentScopeから継承する場合、次のようになります。
(スペースを節約するために、anArray
3つの別々の灰色のリテラルを持つ単一の青いオブジェクトではなく、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つの新しいオブジェクトプロパティを取得します。
要点:
最後のシナリオ:
delete childScope.anArray
childScope.anArray[1] === 22 // true
最初にchildScopeプロパティを削除し、次にプロパティに再度アクセスしようとすると、プロトタイプチェーンが参照されます。
候補者:
scope: true
、directivewith transclude: true
。scope: { ... }
ます。これにより、代わりに「分離」スコープが作成されます。デフォルトでは、ディレクティブは新しいスコープを作成しないことに注意してください。つまり、デフォルトはですscope: false
。
コントローラに次のようなものがあるとします。
$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は、親スコープからプロトタイプを継承する新しい子スコープを生成します。
最初の入力テキストボックスに「77」と入力すると、子スコープmyPrimitive
は、同じ名前の親スコーププロパティを非表示/シャドウにする新しいスコーププロパティを取得します。これはおそらくあなたが望む/期待するものではありません。
2番目の入力テキストボックスに「99」と入力しても、新しい子プロパティは作成されません。tpl2.htmlはモデルをオブジェクトプロパティにバインドするため、プロトタイプの継承は、ngModelがオブジェクトmyObjectを検索するときに開始され、親スコープで検出されます。
モデルをプリミティブからオブジェクトに変更したくない場合は、最初のテンプレートを$parentを使用するように書き直すことができます。
<input ng-model="$parent.myPrimitive">
この入力テキストボックスに「22」と入力しても、新しい子プロパティは作成されません。これで、モデルは親スコープのプロパティにバインドされます($ 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-includeと同じように機能します。したがって、親スコープのプリミティブへの双方向データバインディングが必要な場合は、$ parentを使用するか、モデルをオブジェクトに変更してから、そのオブジェクトのプロパティにバインドします。これにより、親スコープのプロパティの子スコープの非表示/シャドウイングが回避されます。
AngularJS、switch-caseのバインドスコープも参照してください。
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では、各子スコープはnum
myArrayOfPrimitives配列から独立したプロパティを取得します。
このng-repeatは機能しません(あなたが望む/期待するように)。テキストボックスに入力すると、子スコープにのみ表示される灰色のボックスの値が変更されます。必要なのは、入力がmyArrayOfPrimitives配列に影響を与えることであり、子スコープのプリミティブプロパティではありません。これを実現するには、モデルをオブジェクトの配列に変更する必要があります。
したがって、アイテムがオブジェクトの場合、元のオブジェクト(コピーではない)への参照が新しい子スコーププロパティに割り当てられます。子スコーププロパティの値を変更すると(つまり、ng-modelを使用してobj.num
)、親スコープが参照するオブジェクトが変更されます。したがって、上記の2番目のng-repeatでは、次のようになります。
(私は、それがどこに向かっているのかが明確になるように、1本の線を灰色に着色しました。)
これは期待どおりに機能します。テキストボックスに入力すると、灰色のボックスの値が変更され、子スコープと親スコープの両方に表示されます。
ng-model、ng-repeat、inputsの難易度および https://stackoverflow.com/a/13782671/215945も参照してください。
ng-controllerを使用してコントローラーをネストすると、ng-includeやng-switchと同様に、通常のプロトタイプの継承が行われるため、同じ手法が適用されます。ただし、「2つのコントローラーが$ scope継承を介して情報を共有することは悪い形式と見なされます」-http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ サービスを使用してデータを共有する必要があります代わりにコントローラー。
(コントローラースコープの継承を介してデータを本当に共有したい場合は、何もする必要はありません。子スコープはすべての親スコープのプロパティにアクセスできます。「コントローラーのロード順序は、ロードまたはナビゲート時に異なります」も参照してください)
scope: false
)-ディレクティブは新しいスコープを作成しないため、ここには継承はありません。これは簡単ですが、危険でもあります。たとえば、ディレクティブは、実際には既存のプロパティを破壊しているのに、スコープ上に新しいプロパティを作成していると見なす可能性があるためです。これは、再利用可能なコンポーネントとして意図されたディレクティブを作成するのに適していません。scope: true
-ディレクティブは、親スコープからプロトタイプを継承する新しい子スコープを作成します。(同じDOM要素上の)複数のディレクティブが新しいスコープを要求する場合、1つの新しい子スコープのみが作成されます。「通常の」プロトタイプの継承があるため、これはng-includeやng-switchに似ています。したがって、親スコーププリミティブへの双方向データバインディング、および親スコーププロパティの子スコープの非表示/シャドウイングに注意してください。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"
transclude: true
-ディレクティブは、新しい「トランスクルージョンされた」子スコープを作成します。これは、通常、親スコープから継承します。トランスクルージョンされたスコープと分離されたスコープ(存在する場合)は兄弟です。各スコープの$ parentプロパティは、同じ親スコープを参照します。トランスクルージョンスコープと分離スコープの両方が存在する場合、分離スコーププロパティ$$nextSiblingはトランスクルージョンスコープを参照します。トランスクルージョンされたスコープのニュアンスはわかりません。
transclude: true
このフィドルにはshowScope()
、分離されたスコープとトランスクルージョンされたスコープを調べるために使用できる機能があります。フィドルのコメントの説明を参照してください。
スコープには次の4つのタイプがあります。
scope: true
scope: {...}
。これはプロトタイプではありませんが、「=」、「@」、および「&」は、属性を介して親スコープのプロパティにアクセスするメカニズムを提供します。transclude: true
。これも通常のプロトタイプスコープの継承ですが、分離スコープの兄弟でもあります。すべてのスコープ(プロトタイプかどうか)について、Angularは常にプロパティ$parentと$$childHeadと$$childTailを介して、親子関係(つまり階層)を追跡します。
ダイアグラムは、 githubにあるgraphviz "*.dot"ファイルを使用して生成されました。TimCaswellの「オブジェクトグラフを使用したJavaScriptの学習」は、ダイアグラムにGraphVizを使用するためのインスピレーションでした。
私は決してマークの答えと競合したくありませんが、 Javascript の継承とそのプロトタイプ チェーンの初心者として、最終的にすべてがクリックされた部分を強調したかっただけです。
プロパティの読み取りのみがプロトタイプ チェーンを検索し、書き込みは検索しません。だからあなたが設定するとき
myObject.prop = '123';
チェーンを調べませんが、設定すると
myObject.myThing.prop = '123';
その prop に書き込む前に myThing を検索しようとする、その書き込み操作内で微妙な読み取りが行われています。そのため、子から object.properties に書き込むと、親のオブジェクトが取得されます。
@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