APIリファレンスScopeページは言います:
スコープは親スコープから継承できます。
開発者ガイドScopeページは言います:
スコープは(典型的には)親スコープからプロパティを継承します。
- では、子スコープは常に親スコープからプロトタイプを継承しますか?
- 例外はありますか?
- 継承する場合、それは常に通常のJavaScriptプロトタイプの継承ですか?
APIリファレンスScopeページは言います:
スコープは親スコープから継承できます。
開発者ガイドScopeページは言います:
スコープは(典型的には)親スコープからプロパティを継承します。
回答:
簡単な答え:
子スコープは通常、親スコープからプロトタイプを継承しますが、常に継承するわけではありません。このルールの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が親スコープからプロトタイプを継承する場合、次のようになります。
(スペースを節約するために、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
、withwithディレクティブ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
(myArrayOfPrimitivesのように)itemがプリミティブである場合、基本的には値のコピーが新しい子スコーププロパティに割り当てられます。子スコープのプロパティの値を変更しても(つまり、ng-modelを使用しているため、子スコープを使用num
)、親スコープが参照する配列は変更されません。したがって、上記の最初のng-repeatでは、各子スコープはnum
myArrayOfPrimitives配列から独立したプロパティを取得します。
このng-repeatは機能しません(望みどおりに)。テキストボックスに入力すると、灰色のボックスの値が変更されます。これは、子スコープでのみ表示されます。ここで必要なのは、入力が子スコープのプリミティブプロパティではなく、myArrayOfPrimitives配列に影響を与えることです。これを行うには、モデルをオブジェクトの配列に変更する必要があります。
したがって、アイテムがオブジェクトの場合、元のオブジェクト(コピーではない)への参照が新しい子スコーププロパティに割り当てられます。子スコーププロパティの値を変更すると(つまり、ng-modelを使用し、したがってobj.num
)、親スコープが参照するオブジェクトが変更されます。上記の2回目のng-repeatでは、次のようになります。
(どこに行くのか明確にするために、1本の線を灰色に着色しました。)
これは期待どおりに機能します。テキストボックスに入力すると、灰色のボックスの値が変更され、子スコープと親スコープの両方に表示されます。
参照してくださいNG-モデルの難しさ、NGリピート、および入力をして 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__
参照オブジェクトを分離します。分離スコープの$ parentは親スコープを参照するため、分離され、親スコープからプロトタイプを継承しませんが、それでも子スコープです。
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
と
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にある「* .dot」ファイル。Tim Caswellの「オブジェクトグラフを使用したJavaScriptの学習」は、図にGraphVizを使用するきっかけになりました。
__proto__
参照オブジェクトを分離する」。代わりに「スコープの__proto__
参照を分離するScopeオブジェクト」である必要があります。したがって、最後の2つの画像では、オレンジ色の「オブジェクト」ボックスは「スコープ」ボックスである必要があります。
私はマークの答えと決して競合したくありませんが、Javascriptの継承とそのプロトタイプチェーンの初心者としてすべてが最後にすべてをクリックするようにした作品を強調したかっただけです。
プロパティの読み取りのみがプロトタイプチェーンを検索し、書き込みは行いません。だからあなたが設定したとき
myObject.prop = '123';
チェーンを調べませんが、設定すると
myObject.myThing.prop = '123';
プロップに書き込む前に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"を子プロトタイプに追加します。これにより、子オブジェクトの "primitive"プロパティ値が書き換えられ、子オブジェクトと親オブジェクトの両方で "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をご覧ください。