La página Ámbito de referencia de API dice:
Un ámbito puede heredar de un ámbito primario.
La página Alcance de la Guía del desarrollador dice:
Un ámbito (prototípicamente) hereda propiedades de su ámbito principal.
- Entonces, ¿un ámbito secundario siempre hereda prototípicamente de su ámbito primario?
- ¿Hay excepciones?
- Cuando hereda, ¿es siempre una herencia prototípica de JavaScript normal?
javascript
angularjs
inheritance
prototype
prototypal-inheritance
Mark Rajcok
fuente
fuente
Respuestas:
Respuesta rápida :
un ámbito secundario normalmente hereda prototípicamente de su ámbito primario, pero no siempre. Una excepción a esta regla es una directiva con
scope: { ... }
: esto crea un alcance de "aislamiento" que no hereda prototípicamente. Esta construcción se usa a menudo al crear una directiva de "componente reutilizable".En cuanto a los matices, la herencia del alcance es normalmente directa ... hasta que necesite un enlace de datos bidireccional (es decir, elementos de formulario, modelo ng) en el alcance secundario. Ng-repeat, ng-switch y ng-include pueden hacer que te tropieces si intentas unirte a una primitiva (por ejemplo, número, cadena, booleano) en el ámbito primario desde dentro del ámbito secundario. No funciona de la manera en que la mayoría de la gente espera que funcione. El ámbito secundario obtiene su propia propiedad que oculta / sombrea la propiedad principal del mismo nombre. Sus soluciones son
AngularJS nuevos desarrolladores a menudo no se dan cuenta de que
ng-repeat
,ng-switch
,ng-view
,ng-include
yng-if
todo ello crea nuevos ámbitos secundarios, por lo que el problema aparece a menudo cuando se trata de estas directivas. (Vea este ejemplo para una ilustración rápida del problema).Este problema con las primitivas se puede evitar fácilmente siguiendo la "mejor práctica" de tener siempre un '.' en sus modelos ng : mire 3 minutos. Misko demuestra la primitiva cuestión vinculante con
ng-switch
.Teniendo un '.' en sus modelos se asegurará de que la herencia prototípica esté en juego. Entonces, usa
Respuesta larga :
Herencia de prototipos de JavaScript
También se coloca en el wiki de AngularJS: https://github.com/angular/angular.js/wiki/Understanding-Scopes
Es importante tener una comprensión sólida de la herencia de prototipos, especialmente si proviene de un entorno del lado del servidor y está más familiarizado con la herencia clásica. Así que repasemos eso primero.
Supongamos que parentScope tiene propiedades aString, aNumber, anArray, anObject y aFunction. Si childScope hereda prototípicamente de parentScope, tenemos:
(Tenga en cuenta que para ahorrar espacio, muestro el
anArray
objeto como un único objeto azul con sus tres valores, en lugar de un solo objeto azul con tres literales grises separados).Si intentamos acceder a una propiedad definida en parentScope desde el ámbito secundario, JavaScript primero buscará en el ámbito secundario, no encontrará la propiedad, luego buscará en el ámbito heredado y encontrará la propiedad. (Si no encuentra la propiedad en parentScope, continuará en la cadena de prototipos ... hasta el alcance raíz). Entonces, todo esto es cierto:
Supongamos que luego hacemos esto:
No se consulta la cadena del prototipo y se agrega una nueva propiedad aString al childScope. Esta nueva propiedad oculta / sombrea la propiedad parentScope con el mismo nombre. Esto será muy importante cuando discutamos ng-repeat y ng-include a continuación.
Supongamos que luego hacemos esto:
Se consulta la cadena del prototipo porque los objetos (anArray y anObject) no se encuentran en childScope. Los objetos se encuentran en parentScope y los valores de las propiedades se actualizan en los objetos originales. No se agregan nuevas propiedades a childScope; No se crean nuevos objetos. (Tenga en cuenta que en JavaScript las matrices y funciones también son objetos).
Supongamos que luego hacemos esto:
No se consulta la cadena del prototipo, y el ámbito secundario obtiene dos nuevas propiedades de objeto que ocultan / ocultan las propiedades del objeto parentScope con los mismos nombres.
Comida para llevar:
Un último escenario:
Primero eliminamos la propiedad childScope, luego, cuando intentamos acceder a la propiedad nuevamente, se consulta la cadena del prototipo.
Herencia de alcance angular
Los contendientes:
scope: true
, directive withtransclude: true
.scope: { ... }
. Esto crea un alcance "aislado" en su lugar.Tenga en cuenta que, por defecto, las directivas no crean un nuevo ámbito, es decir, el valor predeterminado es
scope: false
.ng-include
Supongamos que tenemos en nuestro controlador:
Y en nuestro HTML:
Cada ng-include genera un nuevo ámbito secundario, que hereda prototípicamente del ámbito primario.
Al escribir (digamos, "77") en el primer cuadro de texto de entrada, el ámbito secundario obtiene una nueva
myPrimitive
propiedad de ámbito que oculta / oculta la propiedad de ámbito principal del mismo nombre. Esto probablemente no sea lo que quieres / esperas.Escribir (digamos, "99") en el segundo cuadro de texto de entrada no da como resultado una nueva propiedad secundaria. Debido a que tpl2.html vincula el modelo a una propiedad de objeto, la herencia prototípica se activa cuando ngModel busca el objeto myObject, lo encuentra en el ámbito principal.
Podemos reescribir la primera plantilla para usar $ parent, si no queremos cambiar nuestro modelo de primitivo a objeto:
Escribir (por ejemplo, "22") en este cuadro de texto de entrada no da como resultado una nueva propiedad secundaria. El modelo ahora está vinculado a una propiedad del ámbito primario (porque $ parent es una propiedad de ámbito secundario que hace referencia al ámbito primario).
Para todos los ámbitos (prototipo o no), Angular siempre rastrea una relación padre-hijo (es decir, una jerarquía), a través de las propiedades de ámbito $ parent, $$ childHead y $$ childTail. Normalmente no muestro estas propiedades de alcance en los diagramas.
Para escenarios donde los elementos de formulario no están involucrados, otra solución es definir una función en el ámbito primario para modificar la primitiva. Luego, asegúrese de que el niño siempre llame a esta función, que estará disponible para el alcance del niño debido a la herencia prototípica. P.ej,
Aquí hay un violín de muestra que utiliza este enfoque de "función principal". (El violín se escribió como parte de esta respuesta: https://stackoverflow.com/a/14104318/215945 ).
Consulte también https://stackoverflow.com/a/13782671/215945 y https://github.com/angular/angular.js/issues/1267 .
ng-switch
La herencia del alcance ng-switch funciona igual que ng-include. Entonces, si necesita un enlace de datos bidireccional a una primitiva en el ámbito primario, use $ parent o cambie el modelo para que sea un objeto y luego enlace a una propiedad de ese objeto. Esto evitará que el ámbito secundario oculte / sombree las propiedades del ámbito primario.
Ver también AngularJS, enlace de alcance de un caso de interruptor?
ng-repeat
Ng-repeat funciona un poco diferente. Supongamos que tenemos en nuestro controlador:
Y en nuestro HTML:
Para cada elemento / iteración, ng-repeat crea un nuevo ámbito, que hereda prototípicamente del ámbito principal, pero también asigna el valor del elemento a una nueva propiedad en el nuevo ámbito secundario . (El nombre de la nueva propiedad es el nombre de la variable de bucle). Esto es lo que el código fuente angular para ng-repeat es:
Si el elemento es primitivo (como en myArrayOfPrimitives), esencialmente se asigna una copia del valor a la nueva propiedad de ámbito secundario. Cambiar el valor de la propiedad del ámbito secundario (es decir, usar ng-model, por lo tanto, el ámbito secundario
num
) no cambia la matriz a la que hace referencia el ámbito primario. Entonces, en la primera repetición ng anterior, cada ámbito secundario obtiene unanum
propiedad que es independiente de la matriz myArrayOfPrimitives:Esta repetición de ng no funcionará (como desea / espera). Escribir en los cuadros de texto cambia los valores en los cuadros grises, que solo son visibles en los ámbitos secundarios. Lo que queremos es que las entradas afecten a la matriz myArrayOfPrimitives, no a una propiedad primitiva de ámbito secundario. Para lograr esto, necesitamos cambiar el modelo para que sea una matriz de objetos.
Por lo tanto, si el elemento es un objeto, se asigna una referencia al objeto original (no una copia) a la nueva propiedad de ámbito secundario. Cambiando el valor de la propiedad ámbito secundario (es decir, usando ng de modelo, por lo tanto
obj.num
) hace cambiar el objeto de las referencias ámbito padre. Entonces, en la segunda repetición ng anterior, tenemos:(Coloreé una línea de gris para que quede claro a dónde va).
Esto funciona como se esperaba. Escribir en los cuadros de texto cambia los valores en los cuadros grises, que son visibles para los ámbitos primarios y secundarios.
Consulte también Dificultad con ng-model, ng-repeat y input y https://stackoverflow.com/a/13782671/215945
ng-controller
La anidación de controladores que usan ng-controller da como resultado una herencia prototípica normal, al igual que ng-include y ng-switch, por lo que se aplican las mismas técnicas. Sin embargo, "se considera una mala forma para que dos controladores compartan información a través de la herencia de $ alcance" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Se debe utilizar un servicio para compartir datos entre controladores en su lugar.
(Si realmente desea compartir datos a través de la herencia del alcance de los controladores, no hay nada que deba hacer. El alcance secundario tendrá acceso a todas las propiedades del alcance primario. Consulte también El orden de carga del controlador difiere al cargar o navegar )
directivas
scope: false
): la directiva no crea un nuevo ámbito, por lo que no hay herencia aquí. Esto es fácil, pero también peligroso porque, por ejemplo, una directiva podría pensar que está creando una nueva propiedad en el ámbito, cuando en realidad está bloqueando una propiedad existente. Esta no es una buena opción para escribir directivas destinadas a componentes reutilizables.scope: true
- la directiva crea un nuevo ámbito secundario que hereda prototípicamente del ámbito primario. Si más de una directiva (en el mismo elemento DOM) solicita un nuevo ámbito, solo se crea un nuevo ámbito secundario. Como tenemos una herencia prototípica "normal", esto es como ng-include y ng-switch, así que tenga cuidado con el enlace de datos bidireccional a las primitivas del alcance primario y el ocultamiento / sombreado del alcance secundario de las propiedades del alcance primario.scope: { ... }
- la directiva crea un nuevo aislamiento / alcance aislado. No hereda prototípicamente. Esta suele ser su mejor opción al crear componentes reutilizables, ya que la directiva no puede leer o modificar accidentalmente el ámbito principal. Sin embargo, tales directivas a menudo necesitan acceso a algunas propiedades de ámbito principal. El hash del objeto se usa para configurar un enlace bidireccional (usando '=') o un enlace unidireccional (usando '@') entre el alcance principal y el alcance aislado. También hay '&' para enlazar a las expresiones de ámbito principal. Por lo tanto, todos estos crean propiedades de ámbito local que se derivan del ámbito primario. Tenga en cuenta que los atributos se usan para ayudar a configurar el enlace: no solo puede hacer referencia a los nombres de propiedades del ámbito primario en el hash del objeto, debe usar un atributo. Por ejemplo, esto no funcionará si desea vincular a la propiedad principalparentProp
en el ámbito aislado:<div my-directive>
yscope: { localProp: '@parentProp' }
. Se debe usar un atributo para especificar cada propiedad principal a la que la directiva desea enlazar:<div my-directive the-Parent-Prop=parentProp>
yscope: { localProp: '@theParentProp' }
.Aislar las
__proto__
referencias del alcance Objeto. Aislar $ parent del ámbito hace referencia al ámbito principal, por lo que, aunque está aislado y no hereda prototípicamente del ámbito principal, sigue siendo un ámbito secundario.Para la imagen a continuación que tenemos
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
yscope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
también, supongamos que la directiva hace esto en su función de enlace:
scope.someIsolateProp = "I'm isolated"
Para obtener más información sobre los ámbitos de aislamiento, consulte http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- la directiva crea un nuevo ámbito secundario "transcluido", que hereda prototípicamente del ámbito primario. El alcance transcluido y el aislado (si corresponde) son hermanos: la propiedad $ parent de cada alcance hace referencia al mismo alcance padre. Cuando existen un alcance transcluido y uno aislado, la propiedad de alcance aislado $$ nextSibling hará referencia al alcance transcluido. No conozco ningún matiz con el alcance transcluido.Para la imagen a continuación, asuma la misma directiva que la anterior con esta adición:
transclude: true
Este violín tiene una
showScope()
función que puede usarse para examinar un alcance aislado y transcluido. Vea las instrucciones en los comentarios en el violín.Resumen
Hay cuatro tipos de ámbitos:
scope: true
scope: {...}
. Este no es prototipo, pero '=', '@' y '&' proporcionan un mecanismo para acceder a las propiedades del ámbito primario, a través de atributos.transclude: true
. Este también es una herencia normal del alcance prototípico, pero también es un hermano de cualquier alcance aislado.Para todos los ámbitos (prototipo o no), Angular siempre rastrea una relación padre-hijo (es decir, una jerarquía), a través de las propiedades $ parent y $$ childHead y $$ childTail.
Los diagramas se generaron con graphvizArchivos "* .dot", que están en github . " Aprendiendo JavaScript con gráficos de objetos " de Tim Caswell fue la inspiración para usar GraphViz para los diagramas.
fuente
__proto__
referencias del alcance Objeto". en su lugar debería ser "Aislar las__proto__
referencias del alcance a un objeto Scope". Por lo tanto, en las últimas dos imágenes, los cuadros naranjas de "Objeto" deberían ser cuadros de "Alcance".De ninguna manera quiero competir con la respuesta de Mark, pero solo quería resaltar la pieza que finalmente hizo que todo haga clic como alguien nuevo en la herencia de Javascript y su cadena de prototipos .
Solo las lecturas de propiedades buscan en la cadena del prototipo, no las escrituras. Entonces, cuando configuras
No mira la cadena, pero cuando configuras
hay una lectura sutil dentro de esa operación de escritura que intenta buscar myThing antes de escribir en su utilería. Es por eso que escribir en object.properties del niño llega a los objetos del padre.
fuente
Me gustaría agregar un ejemplo de herencia prototípica con javascript a la respuesta de @Scott Driscoll. Utilizaremos el patrón de herencia clásico con Object.create (), que forma parte de la especificación EcmaScript 5.
Primero creamos la función de objeto "Padre"
Luego agregue un prototipo a la función de objeto "Principal"
Crear función de objeto "Hijo"
Asignar prototipo hijo (Hacer que el prototipo hijo herede del prototipo padre)
Asignar el constructor prototipo "Niño" apropiado
Agregue el método "changeProps" a un prototipo secundario, que reescribirá el valor de la propiedad "primitiva" en el objeto secundario y cambiará el valor "object.one" tanto en los objetos secundarios como primarios
Iniciar objetos padre (padre) e hijo (hijo).
Llame al niño (hijo) método changeProps
Comprueba los resultados.
La propiedad primitiva padre no cambió
Propiedad primitiva secundaria modificada (reescrita)
Las propiedades object.one padre y niño cambiaron
Ejemplo de trabajo aquí http://jsbin.com/xexurukiso/1/edit/
Más información sobre Object.create aquí https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create
fuente