La page Portée de référence de l'API indique:
Une étendue peut hériter d'une étendue parent.
La page Portée du Guide du développeur indique:
Une portée (de manière prototypique) hérite des propriétés de sa portée parent.
- Ainsi, une portée enfant hérite-t-elle toujours de manière prototypique de sa portée parent?
- Y a-t-il des exceptions?
- Lorsqu'il hérite, est-ce toujours un héritage prototypique JavaScript normal?
Réponses:
Réponse rapide :
une portée enfant hérite normalement de sa portée parent de manière prototypique, mais pas toujours. Une exception à cette règle est une directive avec
scope: { ... }
- cela crée une portée "isolée" qui n'hérite pas de manière prototypique. Cette construction est souvent utilisée lors de la création d'une directive "composant réutilisable".En ce qui concerne les nuances, l'héritage de portée est normalement simple ... jusqu'à ce que vous ayez besoin d' une liaison de données bidirectionnelle (c'est-à-dire, des éléments de formulaire, un modèle ng) dans la portée enfant. Ng-repeat, ng-switch et ng-include peuvent vous déclencher si vous essayez de vous lier à une primitive (par exemple, nombre, chaîne, booléen) dans la portée parent depuis l'intérieur de la portée enfant. Cela ne fonctionne pas comme la plupart des gens pensent que cela devrait fonctionner. La portée enfant obtient sa propre propriété qui masque / masque la propriété parent du même nom. Vos solutions de contournement sont
Les développeurs nouveaux AngularJS souvent ne réalisent pas que
ng-repeat
,ng-switch
,ng-view
,ng-include
etng-if
tout créer de nouveaux champs d' application de l' enfant, de sorte que le problème se manifeste souvent lorsque ces directives sont impliqués. (Voir cet exemple pour une illustration rapide du problème.)Ce problème avec les primitives peut être facilement évité en suivant la "meilleure pratique" de toujours avoir un '.' dans vos modèles ng - regardez 3 minutes. Misko démontre le problème de liaison primitif avec
ng-switch
.Avoir un '.' dans vos modèles garantira que l'héritage prototypique est en jeu. Alors, utilisez
Réponse longue :
Héritage prototypique JavaScript
Également placé sur le wiki AngularJS: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Il est important d'avoir d'abord une solide compréhension de l'héritage prototypique, surtout si vous venez d'un arrière-plan côté serveur et que vous êtes plus familier avec l'héritage classique. Examinons donc cela en premier.
Supposons que parentScope possède les propriétés aString, aNumber, anArray, anObject et aFunction. Si childScope hérite de manière prototype de parentScope, nous avons:
(Notez que pour économiser de l'espace, je montre l'
anArray
objet comme un seul objet bleu avec ses trois valeurs, plutôt que comme un seul objet bleu avec trois littéraux gris distincts.)Si nous essayons d'accéder à une propriété définie sur le parentScope à partir de la portée enfant, JavaScript cherchera d'abord dans la portée enfant, ne trouvera pas la propriété, puis cherchera dans la portée héritée et trouvera la propriété. (S'il ne trouvait pas la propriété dans le parentScope, il continuerait la chaîne du prototype ... jusqu'à la portée racine). Donc, tout cela est vrai:
Supposons que nous fassions ensuite ceci:
La chaîne prototype n'est pas consultée et une nouvelle propriété aString est ajoutée à childScope. Cette nouvelle propriété masque / masque la propriété parentScope du même nom. Cela deviendra très important lorsque nous discuterons de ng-repeat et ng-include ci-dessous.
Supposons que nous fassions ensuite ceci:
La chaîne prototype est consultée car les objets (anArray et anObject) ne sont pas trouvés dans childScope. Les objets se trouvent dans le parentScope et les valeurs des propriétés sont mises à jour sur les objets d'origine. Aucune nouvelle propriété n'est ajoutée à childScope; aucun nouvel objet n'est créé. (Notez que dans les tableaux et fonctions JavaScript sont également des objets.)
Supposons que nous fassions ensuite ceci:
La chaîne de prototype n'est pas consultée et la portée enfant obtient deux nouvelles propriétés d'objet qui masquent / masquent les propriétés d'objet parentScope avec les mêmes noms.
Points à retenir:
Un dernier scénario:
Nous avons d'abord supprimé la propriété childScope, puis lorsque nous essayons d'accéder à nouveau à la propriété, la chaîne de prototype est consultée.
Héritage de portée angulaire
Les prétendants:
scope: true
, directive withtransclude: true
.scope: { ... }
. Cela crée une portée "isoler" à la place.Notez que, par défaut, les directives ne créent pas de nouvelle portée - c'est-à-dire que la valeur par défaut est
scope: false
.ng-include
Supposons que nous ayons dans notre contrôleur:
Et dans notre HTML:
Chaque ng-include génère une nouvelle portée enfant, qui hérite de manière prototypique de la portée parent.
En tapant (par exemple, "77") dans la première zone de texte d'entrée, la portée enfant obtient une nouvelle
myPrimitive
propriété de portée qui masque / masque la propriété de portée parent du même nom. Ce n'est probablement pas ce que vous voulez / attendez.Taper (par exemple, "99") dans la deuxième zone de texte d'entrée n'entraîne pas une nouvelle propriété enfant. Étant donné que tpl2.html lie le modèle à une propriété d'objet, l'héritage prototypique intervient lorsque le ngModel recherche l'objet myObject - il le trouve dans la portée parent.
Nous pouvons réécrire le premier modèle à utiliser $ parent, si nous ne voulons pas changer notre modèle d'une primitive en un objet:
Taper (par exemple, "22") dans cette zone de texte d'entrée n'entraîne pas de nouvelle propriété enfant. Le modèle est désormais lié à une propriété de la portée parent (car $ parent est une propriété de portée enfant qui fait référence à la portée parent).
Pour toutes les étendues (prototypiques ou non), Angular suit toujours une relation parent-enfant (c'est-à-dire une hiérarchie), via les propriétés d'étendue $ parent, $$ childHead et $$ childTail. Normalement, je ne montre pas ces propriétés de portée dans les diagrammes.
Pour les scénarios où les éléments de formulaire ne sont pas impliqués, une autre solution consiste à définir une fonction sur la portée parent pour modifier la primitive. Assurez-vous ensuite que l'enfant appelle toujours cette fonction, qui sera disponible pour la portée enfant en raison de l'héritage prototypique. Par exemple,
Voici un exemple de violon qui utilise cette approche de «fonction parent». (Le violon a été écrit dans le cadre de cette réponse: https://stackoverflow.com/a/14104318/215945 .)
Voir également https://stackoverflow.com/a/13782671/215945 et https://github.com/angular/angular.js/issues/1267 .
ng-switch
L'héritage de la portée de ng-switch fonctionne exactement comme ng-include. Donc, si vous avez besoin d'une liaison de données bidirectionnelle à une primitive dans la portée parent, utilisez $ parent ou modifiez le modèle pour qu'il soit un objet, puis liez-le à une propriété de cet objet. Cela évitera que la portée enfant ne masque / masque les propriétés de la portée parent.
Voir aussi AngularJS, bind scope of a switch-case?
ng-repeat
Ng-repeat fonctionne un peu différemment. Supposons que nous ayons dans notre contrôleur:
Et dans notre HTML:
Pour chaque élément / itération, ng-repeat crée une nouvelle étendue, qui hérite de manière prototypique de l'étendue parent, mais il affecte également la valeur de l'élément à une nouvelle propriété sur la nouvelle étendue enfant . (Le nom de la nouvelle propriété est le nom de la variable de boucle.) Voici ce qu'est réellement le code source angulaire pour ng-repeat:
Si item est une primitive (comme dans myArrayOfPrimitives), une copie de la valeur est essentiellement affectée à la nouvelle propriété de portée enfant. La modification de la valeur de la propriété de la portée enfant (c'est-à-dire en utilisant ng-model, donc la portée enfant
num
) ne change pas le tableau auquel la portée parent fait référence. Ainsi, dans la première répétition ng ci-dessus, chaque portée enfant obtient unenum
propriété indépendante du tableau myArrayOfPrimitives:Cette répétition ng ne fonctionnera pas (comme vous le souhaitez / attendez). La saisie dans les zones de texte modifie les valeurs dans les zones grises, qui ne sont visibles que dans les étendues enfants. Ce que nous voulons, c'est que les entrées affectent le tableau myArrayOfPrimitives, et non une propriété primitive de portée enfant. Pour ce faire, nous devons changer le modèle pour qu'il soit un tableau d'objets.
Ainsi, si l'élément est un objet, une référence à l'objet d'origine (pas une copie) est affectée à la nouvelle propriété de portée enfant. Modification de la valeur de la propriété portée des enfants (à l'aide ng-modèle, donc
obj.num
) fait changer l'objet les références de la portée des parents. Donc, dans la deuxième répétition ng ci-dessus, nous avons:(J'ai coloré une ligne en gris juste pour qu'il soit clair où il va.)
Cela fonctionne comme prévu. La saisie dans les zones de texte modifie les valeurs dans les zones grises, qui sont visibles pour les étendues enfant et parent.
Voir aussi Difficulté avec ng-model, ng-repeat et entrées et https://stackoverflow.com/a/13782671/215945
ng-controller
L'imbrication de contrôleurs utilisant ng-controller entraîne un héritage prototypique normal, tout comme ng-include et ng-switch, donc les mêmes techniques s'appliquent. Cependant, "il est considéré comme une mauvaise forme pour deux contrôleurs de partager des informations via l'héritage $ scope" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Un service doit être utilisé pour partager des données entre contrôleurs à la place.
(Si vous voulez vraiment partager des données via l'héritage de l'étendue des contrôleurs, vous n'avez rien à faire. L'étendue enfant aura accès à toutes les propriétés de l'étendue parent. Voir aussi L'ordre de chargement du contrôleur diffère lors du chargement ou de la navigation )
directives
scope: false
) - la directive ne crée pas de nouvelle portée, il n'y a donc pas d'héritage ici. C'est facile, mais aussi dangereux parce que, par exemple, une directive peut penser qu'elle crée une nouvelle propriété sur la portée, alors qu'en fait elle détruit une propriété existante. Ce n'est pas un bon choix pour écrire des directives qui sont conçues comme des composants réutilisables.scope: true
- la directive crée une nouvelle portée enfant qui hérite de manière prototypique de la portée parent. Si plusieurs directives (sur le même élément DOM) demandent une nouvelle portée, une seule nouvelle portée enfant est créée. Étant donné que nous avons un héritage prototypique "normal", cela ressemble à ng-include et ng-switch, alors méfiez-vous de la liaison de données bidirectionnelle aux primitives de portée parent, et du masquage / duplication de la portée enfant des propriétés de la portée parent.scope: { ... }
- la directive crée une nouvelle portée isolée / isolée. Il n'hérite pas de manière prototypique. C'est généralement votre meilleur choix lors de la création de composants réutilisables, car la directive ne peut pas lire ou modifier accidentellement la portée parent. Cependant, ces directives ont souvent besoin d'accéder à quelques propriétés de portée parent. Le hachage d'objet est utilisé pour configurer la liaison bidirectionnelle (en utilisant '=') ou la liaison unidirectionnelle (en utilisant '@') entre la portée parent et la portée isolée. Il y a aussi '&' pour se lier aux expressions de portée parent. Ainsi, ils créent tous des propriétés de portée locale dérivées de la portée parent. Notez que les attributs sont utilisés pour aider à configurer la liaison - vous ne pouvez pas simplement référencer les noms de propriété de portée parent dans le hachage d'objet, vous devez utiliser un attribut. Par exemple, cela ne fonctionnera pas si vous souhaitez vous lier à la propriété parentparentProp
dans le domaine isolé:<div my-directive>
etscope: { localProp: '@parentProp' }
. Un attribut doit être utilisé pour spécifier chaque propriété parent à laquelle la directive veut se lier:<div my-directive the-Parent-Prop=parentProp>
etscope: { localProp: '@theParentProp' }
.Isoler les
__proto__
références de la portée Objet. Le parent $ de la portée d'isolat fait référence à la portée parent, donc bien qu'il soit isolé et n'hérite pas de manière prototypique de la portée parent, il s'agit toujours d'une portée enfant.Pour l'image ci-dessous, nous avons
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
etscope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
supposons également que la directive le fait dans sa fonction de liaison:
scope.someIsolateProp = "I'm isolated"
Pour plus d'informations sur les étendues d'isolement, voir http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- la directive crée une nouvelle portée enfant "transclue", qui hérite de manière prototypique de la portée parent. La portée transclue et la portée isolée (le cas échéant) sont des frères et sœurs - la propriété $ parent de chaque portée fait référence à la même portée parent. Quand une portée transclude et une portée isolate existent toutes les deux, la propriété de portée isolate $$ nextSibling fera référence à la portée transclue. Je ne connais aucune nuance avec la portée transclue.Pour l'image ci-dessous, supposez la même directive que ci-dessus avec cet ajout:
transclude: true
Ce violon a une
showScope()
fonction qui peut être utilisée pour examiner une portée isolée et transclue. Voir les instructions dans les commentaires au violon.Sommaire
Il existe quatre types de portées:
scope: true
scope: {...}
. Celui-ci n'est pas prototypique, mais '=', '@' et '&' fournissent un mécanisme pour accéder aux propriétés de portée parent, via des attributs.transclude: true
. Celui-ci est également un héritage de portée prototypique normal, mais il est également un frère de toute portée d'isolat.Pour toutes les étendues (prototypiques ou non), Angular suit toujours une relation parent-enfant (c'est-à-dire une hiérarchie), via les propriétés $ parent et $$ childHead et $$ childTail.
Des diagrammes ont été générés avec graphvizFichiers "* .dot", qui sont sur github . " Learning JavaScript with Object Graphs " de Tim Caswell a été l'inspiration pour utiliser GraphViz pour les diagrammes.
la source
__proto__
Objet de référence de l'oscilloscope". devrait plutôt être "Isoler les__proto__
références de portée d' un objet Scope". Ainsi, dans les deux dernières images, les cases orange "Objet" devraient plutôt être des cases "Portée".Je ne veux en aucun cas rivaliser avec la réponse de Mark, mais je voulais juste mettre en évidence la pièce qui a finalement fait tout décliner en tant que nouvelle pour l' héritage Javascript et sa chaîne de prototypes .
Seule la propriété lit la recherche dans la chaîne du prototype, pas l'écrit. Donc, quand vous définissez
Il ne regarde pas la chaîne, mais lorsque vous définissez
il y a une lecture subtile en cours dans cette opération d'écriture qui essaie de rechercher myThing avant d'écrire sur son accessoire. C'est pourquoi l'écriture dans object.properties de l'enfant atteint les objets du parent.
la source
Je voudrais ajouter un exemple d'héritage prototypique avec javascript à la réponse @Scott Driscoll. Nous utiliserons un modèle d'héritage classique avec Object.create () qui fait partie de la spécification EcmaScript 5.
Nous créons d'abord la fonction d'objet "Parent"
Ajoutez ensuite un prototype à la fonction d'objet "Parent"
Créer une fonction d'objet "Enfant"
Attribuer un prototype enfant (faire hériter le prototype enfant du prototype parent)
Attribuer le constructeur de prototype "enfant" approprié
Ajoutez la méthode "changeProps" à un prototype enfant, qui réécrira la valeur de propriété "primitive" dans l'objet enfant et changera la valeur "object.one" dans les objets enfant et parent
Initiez les objets Parent (papa) et Enfant (fils).
Appeler la méthode changeProps de Child (son)
Vérifiez les résultats.
La propriété primitive parent n'a pas changé
Propriété primitive enfant modifiée (réécrite)
Les propriétés d'objet.one parent et enfant ont été modifiées
Exemple de travail ici http://jsbin.com/xexurukiso/1/edit/
Plus d'informations sur Object.create ici https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create
la source