A página Escopo de referência da API diz:
Um escopo pode herdar de um escopo pai.
A página Escopo do Guia do desenvolvedor diz:
Um escopo (prototipicamente) herda propriedades do seu escopo pai.
- Então, um escopo filho sempre herda prototipicamente de seu escopo pai?
- Existem exceções?
- Quando ele herda, é sempre uma herança prototípica normal do JavaScript?
javascript
angularjs
inheritance
prototype
prototypal-inheritance
Mark Rajcok
fonte
fonte
Respostas:
Resposta rápida :
um escopo filho normalmente herda prototipicamente do escopo pai, mas nem sempre. Uma exceção a essa regra é uma diretiva com
scope: { ... }
- isso cria um escopo "isolado" que não herda prototipicamente. Essa construção é frequentemente usada ao criar uma diretiva "componente reutilizável".Quanto às nuances, a herança do escopo é normalmente direta ... até que você precise da ligação de dados bidirecional (ou seja, elementos de formulário, modelo ng) no escopo filho. Repetir Ng, alternar entre ng e incluir como ng pode fazer com que você tente se ligar a uma primitiva (por exemplo, número, sequência, booleano) no escopo pai de dentro do escopo filho. Não funciona da maneira que a maioria das pessoas espera que funcione. O escopo filho obtém sua própria propriedade que oculta / oculta a propriedade pai com o mesmo nome. Suas soluções alternativas são
Novos desenvolvedores AngularJS muitas vezes não percebem que
ng-repeat
,ng-switch
,ng-view
,ng-include
eng-if
tudo criar novos escopos filhos, de modo que o problema geralmente aparece quando estas directivas estão envolvidos. (Veja este exemplo para uma ilustração rápida do problema.)Esse problema com os primitivos pode ser facilmente evitado seguindo a "melhor prática" de sempre ter um '.' nos seus modelos ng - assista a 3 minutos. Misko demonstra o problema de ligação primitivo com
ng-switch
.Tendo uma '.' nos seus modelos garantirá que a herança prototípica esteja em jogo. Então use
Resposta longa :
Herança Prototípica do JavaScript
Também colocado no wiki do AngularJS: https://github.com/angular/angular.js/wiki/Understanding-Scopes
É importante primeiro ter um entendimento sólido da herança prototípica, especialmente se você é proveniente de um plano de fundo do servidor e está mais familiarizado com a herança clássica. Então, vamos revisar isso primeiro.
Suponha que parentScope tenha as propriedades aString, aNumber, anArray, anObject e aFunction. Se childScope herda prototipicamente de parentScope, temos:
(Observe que, para economizar espaço, mostro o
anArray
objeto como um único objeto azul com seus três valores, em vez de um único objeto azul com três literais cinza separados.)Se tentarmos acessar uma propriedade definida no parentScope no escopo filho, o JavaScript primeiro procurará no escopo filho, não encontrará a propriedade, depois procurará no escopo herdado e encontrará a propriedade. (Se não encontrasse a propriedade no parentScope, continuaria a cadeia de protótipos ... até o escopo raiz). Então, tudo isso é verdade:
Suponha que façamos o seguinte:
A cadeia de protótipos não é consultada e uma nova propriedade aString é adicionada ao childScope. Essa nova propriedade oculta / oculta a propriedade parentScope com o mesmo nome. Isso se tornará muito importante quando discutirmos ng-repeat e ng-include abaixo.
Suponha que façamos o seguinte:
A cadeia de protótipos é consultada porque os objetos (anArray e anObject) não são encontrados no childScope. Os objetos são encontrados no parentScope e os valores da propriedade são atualizados nos objetos originais. Nenhuma nova propriedade é adicionada ao childScope; nenhum novo objeto é criado. (Observe que em matrizes e funções JavaScript também são objetos.)
Suponha que façamos o seguinte:
A cadeia de protótipos não é consultada e o escopo filho obtém duas novas propriedades de objetos que ocultam / sombream as propriedades do objeto parentScope com os mesmos nomes.
Aprendizado:
Um último cenário:
Excluímos a propriedade childScope primeiro e, quando tentamos acessar a propriedade novamente, a cadeia de protótipos é consultada.
Herança do escopo angular
Os candidatos:
scope: true
, diretiva comtransclude: true
.scope: { ... }
. Isso cria um escopo "isolado".Observe que, por padrão, as diretivas não criam um novo escopo - ou seja, o padrão é
scope: false
.ng-include
Suponha que tenhamos em nosso controlador:
E no nosso HTML:
Cada ng-include gera um novo escopo filho, que herda prototipicamente do escopo pai.
Digitar (por exemplo, "77") na primeira caixa de texto de entrada faz com que o escopo filho obtenha uma nova
myPrimitive
propriedade de escopo que oculta / oculta a propriedade do escopo pai com o mesmo nome. Provavelmente não é isso que você deseja / espera.Digitar (por exemplo, "99") na segunda caixa de texto de entrada não resulta em uma nova propriedade filho. Como tpl2.html vincula o modelo a uma propriedade de objeto, a herança prototípica entra em ação quando o ngModel procura pelo objeto myObject - ele o encontra no escopo pai.
Podemos reescrever o primeiro modelo para usar $ parent, se não quisermos mudar nosso modelo de primitivo para objeto:
Digitar (por exemplo, "22") nesta caixa de texto de entrada não resulta em uma nova propriedade filho. O modelo agora está vinculado a uma propriedade do escopo pai (porque $ parent é uma propriedade do escopo filho que faz referência ao escopo pai).
Para todos os escopos (prototípicos ou não), o Angular sempre rastreia um relacionamento pai-filho (ou seja, uma hierarquia), através das propriedades do escopo $ parent, $$ childHead e $$ childTail. Normalmente não mostro essas propriedades de escopo nos diagramas.
Para cenários em que os elementos do formulário não estão envolvidos, outra solução é definir uma função no escopo pai para modificar a primitiva. Em seguida, verifique se o filho sempre chama essa função, que estará disponível para o escopo filho devido à herança prototípica. Por exemplo,
Aqui está um exemplo de violino que usa essa abordagem de "função pai". (O violino foi escrito como parte desta resposta: https://stackoverflow.com/a/14104318/215945 .)
Consulte também https://stackoverflow.com/a/13782671/215945 e https://github.com/angular/angular.js/issues/1267 .
ng-switch
A herança de escopo ng-switch funciona como ng-include. Portanto, se você precisar de uma ligação de dados bidirecional para uma primitiva no escopo pai, use $ parent ou altere o modelo para ser um objeto e, em seguida, vincule a uma propriedade desse objeto. Isso evitará que o escopo filho oculte / oculte as propriedades do escopo pai.
Veja também AngularJS, escopo de ligação de um caso de switch?
ng-repeat
Ng-repeat funciona um pouco diferente. Suponha que tenhamos em nosso controlador:
E no nosso HTML:
Para cada item / iteração, ng-repeat cria um novo escopo, que herda prototipicamente do escopo pai, mas também atribui o valor do item a uma nova propriedade no novo escopo filho . (O nome da nova propriedade é o nome da variável de loop.) Aqui está o que o código-fonte angular para ng-repeat é realmente:
Se o item for um primitivo (como em myArrayOfPrimitives), essencialmente uma cópia do valor será atribuída à nova propriedade do escopo filho. Alterar o valor da propriedade do escopo filho (ou seja, usar o modelo ng, portanto o escopo filho
num
) não altera a matriz que o escopo pai faz referência. Portanto, na primeira repetição ng acima, cada escopo filho obtém umanum
propriedade independente da matriz myArrayOfPrimitives:Este ng-repeat não funcionará (como você deseja / espera). Digitar nas caixas de texto altera os valores nas caixas cinzas, que são visíveis apenas nos escopos filho. O que queremos é que as entradas afetem a matriz myArrayOfPrimitives, não uma propriedade primitiva do escopo filho. Para fazer isso, precisamos alterar o modelo para ser uma matriz de objetos.
Portanto, se item é um objeto, uma referência ao objeto original (não uma cópia) é atribuída à nova propriedade do escopo filho. Alterando o valor da propriedade âmbito criança (ou seja, usando-ng de modelo, daí
obj.num
) faz mudar o objecto as referências escopo pai. Então, no segundo ng-repeat acima, temos:(Eu pintei uma linha em cinza apenas para que fique claro para onde está indo.)
Isso funciona como esperado. Digitar nas caixas de texto altera os valores nas caixas cinzas, que são visíveis para os escopos filho e pai.
Consulte também Dificuldade com ng-model, ng-repeat e entradas e https://stackoverflow.com/a/13782671/215945
ng-controller
Aninhar controladores usando ng-controller resulta em herança prototípica normal, assim como ng-include e ng-switch, portanto as mesmas técnicas se aplicam. No entanto, "é considerado péssimo para dois controladores compartilhar informações via herança $ scope" - http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Um serviço deve ser usado para compartilhar dados entre controladores.
(Se você realmente deseja compartilhar dados através da herança do escopo dos controladores, não há nada a fazer. O escopo filho terá acesso a todas as propriedades do escopo pai. Consulte também A ordem de carregamento do controlador difere ao carregar ou navegar )
diretrizes
scope: false
) - a diretiva não cria um novo escopo; portanto, não há herança aqui. Isso é fácil, mas também perigoso, porque, por exemplo, uma diretiva pode pensar que está criando uma nova propriedade no escopo, quando na verdade está roubando uma propriedade existente. Essa não é uma boa opção para escrever diretivas que se destinam a componentes reutilizáveis.scope: true
- a diretiva cria um novo escopo filho que herda prototipicamente do escopo pai. Se mais de uma diretiva (no mesmo elemento DOM) solicitar um novo escopo, apenas um novo escopo filho será criado. Como temos herança prototípica "normal", isso é como ng-include e ng-switch, portanto, tenha cuidado com a ligação de dados bidirecional com as primitivas do escopo pai e com o escopo filho oculto / sombreado das propriedades do escopo pai.scope: { ... }
- a diretiva cria um novo escopo isolado / isolado. Não herda prototipicamente. Geralmente, é a melhor opção ao criar componentes reutilizáveis, pois a diretiva não pode ler ou modificar acidentalmente o escopo pai. No entanto, essas diretivas geralmente precisam acessar algumas propriedades do escopo pai. O hash do objeto é usado para configurar a ligação bidirecional (usando '=') ou ligação unidirecional (usando '@') entre o escopo pai e o escopo isolado. Também há '&' para vincular às expressões de escopo pai. Portanto, todos eles criam propriedades de escopo local que são derivadas do escopo pai. Observe que os atributos são usados para ajudar a configurar a ligação - você não pode apenas fazer referência aos nomes de propriedades do escopo pai no hash do objeto; é necessário usar um atributo. Por exemplo, isso não funcionará se você desejar vincular à propriedade paiparentProp
no escopo isolado:<div my-directive>
escope: { localProp: '@parentProp' }
. Um atributo deve ser usado para especificar cada propriedade pai à qual a diretiva deseja vincular:<div my-directive the-Parent-Prop=parentProp>
escope: { localProp: '@theParentProp' }
.Isole as
__proto__
referências do escopo Object. Isolar $ parent do escopo faz referência ao escopo pai, portanto, embora seja isolado e não herda prototipicamente do escopo pai, ainda é um escopo filho.Para a imagem abaixo temos
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
escope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
também, assumir a directiva faz isso na sua função de interligação:
scope.someIsolateProp = "I'm isolated"
Para mais informações sobre escopos isolar ver http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
transclude: true
- a diretiva cria um novo escopo filho "transcluído", que herda prototipicamente do escopo pai. O escopo transcluído e o isolado (se houver) são irmãos - a propriedade $ parent de cada escopo faz referência ao mesmo escopo pai. Quando um escopo transcluído e um isolado existem, a propriedade $$ scopeSibling do escopo isolado fará referência ao escopo transcluído. Não conheço nenhuma nuance com o escopo transcluído.Para a figura abaixo, assuma a mesma diretiva acima com esta adição:
transclude: true
Esse violino tem uma
showScope()
função que pode ser usada para examinar um escopo isolado e transcluído. Veja as instruções nos comentários no violino.Sumário
Existem quatro tipos de escopos:
scope: true
scope: {...}
. Este não é um protótipo, mas '=', '@' e '&' fornecem um mecanismo para acessar as propriedades do escopo pai, por meio de atributos.transclude: true
. Este também é uma herança de escopo prototípico normal, mas também é um irmão de qualquer escopo isolado.Para todos os escopos (prototípicos ou não), o Angular sempre rastreia um relacionamento pai-filho (ou seja, uma hierarquia), através das propriedades $ parent e $$ childHead e $$ childTail.
Os diagramas foram gerados com graphvizArquivos "* .dot", que estão no github . " Aprendendo JavaScript com gráficos de objetos ", de Tim Caswell, foi a inspiração para o uso do GraphViz nos diagramas.
fonte
__proto__
referências do escopo Object." em vez disso, deve ser "Isolar as__proto__
referências do escopo como um objeto Scope". Portanto, nas duas últimas imagens, as caixas laranja "Objeto" devem ser caixas "Escopo".Eu não quero competir com a resposta de Mark, mas só queria destacar a peça que finalmente fez tudo clicar como alguém novo na herança Javascript e em sua cadeia de protótipos .
Somente as leituras de propriedades pesquisam a cadeia de protótipos, não as gravações. Então, quando você define
Ele não procura a cadeia, mas quando você define
há uma leitura sutil acontecendo nessa operação de gravação que tenta procurar o myThing antes de gravar em seu suporte. É por isso que escrever para object.properties a partir da criança chega aos objetos dos pais.
fonte
Gostaria de adicionar um exemplo de herança prototípica com javascript à resposta do @Scott Driscoll. Usaremos o padrão clássico de herança com Object.create (), que faz parte da especificação do EcmaScript 5.
Primeiro, criamos a função de objeto "Pai"
Em seguida, adicione um protótipo à função de objeto "Pai"
Criar função de objeto "Filho"
Atribuir protótipo filho (tornar o protótipo filho herdado do protótipo pai)
Atribua o construtor de protótipo "filho" adequado
Adicione o método "changeProps" a um protótipo filho, que reescreverá o valor da propriedade "primitiva" no objeto Filho e alterará o valor "object.one" nos objetos Filho e Pai.
Iniciar objetos Pai (pai) e Filho (filho).
Chamar método changeProps filho (filho)
Verifique os resultados.
A propriedade primitiva pai não foi alterada
Propriedade primitiva filho alterada (reescrita)
Objeto pai e filho. Uma propriedade foi alterada
Exemplo de trabalho aqui http://jsbin.com/xexurukiso/1/edit/
Mais informações sobre Object.create aqui https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create
fonte