Каковы нюансы области прототипного / прототипического наследования в AngularJS?

на страница области ссылок API говорит:

область можете наследовать от родительской.

на страница области действия руководства разработчика говорит:

область (прототипически) наследует свойства из родительской области.

итак, всегда ли дочерняя область прототипически наследуется от родительской области? Есть ли исключения? Когда он наследует, это всегда нормально Прототипного наследования в JavaScript?

3 ответов


быстрый ответ:
Дочерняя область обычно прототипически наследуется от родительской области, но не всегда. Единственным исключением из этого правила является директива scope: { ... } -- это создает область "изолировать", которая не наследуется прототипически. Эта конструкция часто используется при создании директивы" многоразовый компонент".

что касается нюансов, наследование области обычно прямолинейно... пока не понадобится 2-способ привязки данных (т. е., форма элементы, ng-модель)в дочерней области. Ng-repeat, ng-switch и ng-include могут споткнуться, если вы попытаетесь привязаться к первобытное (например, number, string, boolean)в родительской области из дочерней области. Это работает не так, как большинство людей ожидают, что это должно работать. Дочерняя область получает собственное свойство, которое скрывает / затеняет родительское свойство с тем же именем. Ваши обходные пути

  1. определите объекты в родителе для вашей модели, затем ссылайтесь на a свойство этого объекта в дочернем объекте: parentObj.someProp
  2. используйте $parent.parentScopeProperty (не всегда возможно, но проще, чем 1. где это возможно)
  3. определите функцию в родительской области и вызовите ее из дочернего (не всегда возможно)

новые разработчики AngularJS часто не понимают, что ng-repeat, ng-switch, ng-view, ng-include и ng-if все создают новые дочерние области, поэтому проблема часто появляется, когда эти директивы вовлеченный. (См. для быстрой иллюстрации проблемы.)

этой проблемы с примитивами можно легко избежать, следуя "лучшей практике"всегда есть '.- в ваших НГ-моделях!--68--> – часы 3 минут. Misko демонстрирует примитивную проблему привязки с ng-switch.

С '.- в ваших моделях будет обеспечено прототипное наследование. Так, использовать

<input type="text" ng-model="someObj.prop1">

<!--rather than
<input type="text" ng-model="prop1">`
-->


L-o-n-g ответ:

Прототипное Наследование JavaScript

также размещен на AngularJS wiki: https://github.com/angular/angular.js/wiki/Understanding-Scopes

важно сначала иметь четкое представление о прототипном наследовании, особенно если вы исходите из серверного фона, и вы более знакомы с классовым наследованием. Итак, давайте сначала рассмотрим это.

предположим, что parentScope имеет свойства aString, aNumber, anArray, anObject и aFunction. Если childScope прототипически наследуется от parentScope, у нас есть:

prototypal inheritance

(обратите внимание, что для экономии места, я показываю anArray объект как один синий объект с тремя значениями, А не один синий объект с тремя отдельными серыми литералами.)

если мы попытаемся получить доступ к свойству, определенному в parentScope из дочерней области JavaScript сначала будет искать в дочерней области, а не находить свойство, затем искать в унаследованной области и находить свойство. (Если бы он не нашел свойство в parentScope, он продолжил бы цепочку прототипов... вплоть до корневой области). Итак, все это правда:

childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'

Предположим, мы тогда сделаем это:

childScope.aString = 'child string'

цепочка прототипов не посоветовались, и новое свойство строка добавляется в childScope. этот новое свойство скрывает / затеняет свойство parentScope с тем же именем. это станет очень важным, когда мы обсудим ng-repeat и ng-include ниже.

property hiding

Предположим, мы тогда сделаем это:

childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'

цепочка прототипов консультируется, потому что объекты (anArray и anObject) не найдены в дочерней области. Объекты находятся в parentScope, а значения свойств обновляются для исходных объектов. Нет новых свойств добавляются в childScope; новые объекты не создаются. (Обратите внимание, что в JavaScript массивы и функции также являются объектами.)

follow the prototype chain

Предположим, мы тогда сделаем это:

childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }

цепочка прототипов не консультируется, а дочерняя область получает два новых свойства объекта, которые скрывают / затеняют свойства объекта parentScope с одинаковыми именами.

more property hiding

выводы:

  • если мы читаем childScope.propertyX, а childScope имеет propertyX, тогда с цепочкой прототипов не консультируются.
  • если мы установим childScope.propertyX, прототип цепи не консультируется.

последний сценарий:

delete childScope.anArray
childScope.anArray[1] === 22  // true

сначала мы удалили свойство childScope, а затем, когда мы снова попытаемся получить доступ к свойству, консультируется с цепочкой прототипов.

after removing a child property


Наследование Угловой Области

в претенденты:

  • следующие создают новые области и наследуют прототипически: ng-repeat, ng-include, ng-switch, ng-controller, directive with scope: true директива с transclude: true.
  • следующее создает новую область, которая не наследует прототипически: директива с scope: { ... }. Вместо этого создается область "изолировать".

обратите внимание, по умолчанию, директивы не создают новую область, т. е. по умолчанию scope: false.

ng-include

Предположим, у нас в контроллере:

$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 создает новую дочернюю область, которая прототипически наследуется от родительской области.

ng-include child scopes

ввод (скажем, "77") в первое входное текстовое поле вызывает дочернюю область, чтобы получить новый myPrimitive свойство scope, скрывающее / затеняющее родительское свойство scope с тем же именем. Этот это наверное не то, что вы хотите/ожидаете.

ng-include with a primitive

ввод (скажем, "99") во второе входное текстовое поле не приводит к новому дочернему свойству. Потому что tpl2.html привязывает модель к свойству объекта, прототипное наследование срабатывает, когда ngModel ищет объект myObject - он находит его в родительской области.

ng-include with an object

мы можем переписать первый шаблон для использования $parent, если мы не хотим менять нашу модель с примитива объекту:

<input ng-model="$parent.myPrimitive">

ввод (скажем, "22") в это текстовое поле ввода не приводит к новому дочернему свойству. Модель теперь привязана к свойству родительской области (поскольку $parent - это свойство дочерней области, ссылающееся на родительскую область).

ng-include with $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-переключатель

наследование области ng-switch работает так же, как ng-include. Поэтому, если вам нужна двусторонняя привязка данных к примитиву в родительской области, используйте $parent или измените модель как объект, а затем свяжите ее со свойством объект. Это позволит избежать скрытия/затенения дочерней области родительских свойств области.

см. также AngularJS, область привязки коммутатора?

ng-повторить

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 на самом деле:

childScope = scope.$new();  // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value;  // creates a new childScope property

если элемент является примитивом (как в myArrayOfPrimitives), по существу копия значения присваивается новому дочернему свойству области. Изменение значения свойства дочерней области (т. е. использование ng-модели, следовательно, дочерней области num) делает не изменить массив ссылок родительской области. Таким образом, в первом ng-repeat выше каждая дочерняя область получает num свойство, не зависящее от массива myArrayOfPrimitives:

ng-repeat with primitives

этот ng-повтор не будет работать (как вы хотите/ожидаете). Ввод в текстовые поля изменяет значения в серых полях,которые видны только в дочерних областях. Мы хотим, чтобы входные данные влияли на массив myArrayOfPrimitives, а не на примитивное свойство дочерней области. К для этого нам нужно изменить модель, чтобы она была массивом объектов.

таким образом, если элемент является объектом, ссылка на исходный объект (не копия) назначается новому свойству дочерней области. Изменение значения свойства дочерней области (т. е. использование ng-модели, следовательно obj.num) тут измените объект ссылки на родительскую область. Итак, во втором ng-повторите выше, мы имеем:

ng-repeat with objects

(я покрасил одну строку в серый цвет, чтобы она понятно, куда он идет.)

это работает, как ожидалось. Ввод в текстовые поля изменяет значения в серых полях, которые видны как дочерней, так и родительской областям.

см. также сложность с ng-model, ng-repeat и входами и https://stackoverflow.com/a/13782671/215945

ng-контроллер

контроллеры вложенности с использованием ng-контроллера приводят к нормальному прототипному наследованию, просто как ng-include и NG-switch, поэтому применяются те же методы. Однако "считается плохой формой для двух контроллеров обмениваться информацией через наследование $ scope"-- http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Вместо этого для обмена данными между контроллерами следует использовать службу.

(если вы действительно хотите обмениваться данными через наследование области контроллеров, вам ничего не нужно делать. Дочерняя область будет иметь доступ ко всем родительским свойства области. См. также порядок загрузки контроллера отличается при загрузке или навигации)

директивы

  1. по умолчанию (scope: false) - директива не создает новую область, поэтому здесь нет наследования. Это легко, но также опасно, потому что, например, директива может подумать, что она создает новое свойство в области, когда на самом деле она избивает существующее свойство. Это не лучший выбор для написания директив, которые предназначено как многоразовые компоненты.
  2. scope: true - директива создает новую дочернюю область, которая прототипически наследуется от родительской области. Если несколько директив (на одном элементе DOM) запрашивают новую область, создается только одна дочерняя область. Поскольку у нас есть "нормальное" прототипное наследование, это похоже на ng-include и NG-switch, поэтому будьте осторожны с привязкой данных 2-way к примитивам родительской области и скрытием/затенением дочерней области родительской области свойства.
  3. scope: { ... } - директива создает новую изолированную / изолированную область. Он не наследует прототипически. Это обычно лучший выбор при создании повторно используемых компонентов, так как директива не может случайно прочитать или изменить родительскую область. Однако такие директивы часто нуждаются в доступе к нескольким свойствам родительской области. Хэш объекта используется для настройки двусторонней привязки (с использованием '=') или односторонней привязки (с использованием '@') между родительской областью и областью изоляции. Существует также " & "для привязки к выражениям родительской области. Таким образом, все они создают свойства локальной области, производные от родительской области. Обратите внимание, что атрибуты используются для настройки привязки-вы не можете просто ссылаться на имена свойств родительской области в хэше объекта, вы должны использовать атрибут. Например, это не сработает, если вы хотите привязаться к родительскому свойству parentProp в изолированной области:<div my-directive> и scope: { localProp: '@parentProp' }. Атрибут должен использоваться для указания каждого родительского свойства, которое директива хочет привязать к: <div my-directive the-Parent-Prop=parentProp> и scope: { localProp: '@theParentProp' }.
    изолировать области __proto__ объект references. Изолировать область $parent ссылается на родительскую область, поэтому, хотя она изолирована и не наследуется прототипически от родительской области, она по-прежнему является дочерней областью.
    на картинке ниже
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"> и
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
    кроме того, предположим, что директива делает это в своей функции связывания:scope.someIsolateProp = "I'm isolated"
    isolated scope
    для получения дополнительной информации по изоляции области видимости http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true - директива создает новую дочернюю область "transcluded", которая прототипически наследуется от родительской области. Трансклюдированная и изолированная область (если есть) являются братьями и сестрами-свойство $parent каждой области ссылается на одну и ту же родительскую область. Когда раскрываемый и изолировать объеме существовать, изолировать объем имущества $$ / / сделать будет ссылаться на раскрываемый масштаб. Я не знаю никаких нюансов с трансцендентальной областью.
    для рисунка ниже предположим ту же директиву, что и выше, с этим добавлением:transclude: true
    transcluded scope

этой скрипка есть showScope() функции, которые могут быть использованы для изучения изолят и раскрываемый области. См. инструкции в комментариях к скрипке.


резюме

существует четыре типа scopes:

  1. нормальное наследование прототипной области -- ng-include, ng-switch, ng-controller, директива с scope: true
  2. обычное наследование прототипной области с копией / назначением -- ng-repeat. Каждая итерация ng-repeat создает новую дочернюю область, и эта новая дочерняя область всегда получает новое свойство.
  3. изолировать область действия -- директива с scope: {...}. Это не прототип, но"=", " @ " и " & " предоставляют механизм доступа к родительской области свойства, через атрибуты.
  4. transcluded scope -- директива с transclude: true. Это также обычное прототипное наследование области, но это также брат любой области изолирования.

для всех областей (прототипных или нет) Angular всегда отслеживает отношения "родитель-потомок" (т. е. иерархию) через свойства $parent и $$childHead и $$childTail.

диаграммы были сгенерированы с помощью graphviz "*.точка" файлы, которые находятся на github. Тим Касуэлл это "изучение JavaScript с графиками объектов " был вдохновением для использования GraphViz для диаграмм.


Я никоим образом не хочу конкурировать с ответом Марка, но просто хотел выделить кусок, который, наконец, заставил все щелкнуть как кто-то новый наследование Javascript и его цепочка прототипов.

только свойство читает поиск прототипа цепочки, а не пишет. поэтому, когда вы устанавливаете

myObject.prop = '123';

он не смотрит вверх по цепочке, но когда вы устанавливаете

myObject.myThing.prop = '123';

в этой записи происходит тонкое чтение операция это пытается найти myThing, прежде чем писать на его опору. Вот почему я пишу возражения.свойства из дочернего получает на объекты родителя.


Я хотел бы добавить пример прототипического наследования с javascript в @Scott Driscoll answer. Мы будем использовать классический шаблон наследования с объекта.create (), который является частью спецификации EcmaScript 5.

Сначала мы создаем" родительскую " объектную функцию

function Parent(){

}

затем добавьте прототип в" родительскую " объектную функцию

 Parent.prototype = {
 primitive : 1,
 object : {
    one : 1
   }
}

создать "дочернюю" функцию объекта

function Child(){

}

назначить дочерний прототип (сделать дочерний прототип наследовать от родительского прототипа)

Child.prototype = Object.create(Parent.prototype);

назначить правильный" Дочерний " конструктор прототипа

Child.prototype.constructor = Child;

добавьте метод " changeProps "в дочерний прототип, который перепишет значение свойства" primitive "в дочернем объекте и изменит" объект.одно " значение как в дочерних, так и в родительских объектах

Child.prototype.changeProps = function(){
    this.primitive = 2;
    this.object.one = 2;
};

инициировать родительские (папа) и дочерние (сын) объекты.

var dad = new Parent();
var son = new Child();

вызов Child (son) changeProps метод

son.changeProps();

Регистрация результаты.

родительское свойство примитива не изменилось

console.log(dad.primitive); /* 1 */

дочернее примитивное свойство изменено (переписано)

console.log(son.primitive); /* 2 */

Родительский и дочерний объект.одно свойство изменено

console.log(dad.object.one); /* 2 */
console.log(son.object.one); /* 2 */

рабочий пример здесь http://jsbin.com/xexurukiso/1/edit/

дополнительная информация об объекте.создать здесь https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create