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


The API Reference Scope page говорит:

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

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

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

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

3 981

3 ответа:

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

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

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

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

этой проблемы с примитивами можно легко избежать, следуя "лучшей практике"всегда есть '. в НГ-модели – смотреть 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 имеет свойства строка, число, массив, объект и функцию. Если 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'

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

property hiding

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

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

цепочка прототипов консультируется, потому что объекты (anArray и anObject) не найдены в childScope. Объекты находятся в родительской области, а значения свойств обновляются в исходных объектах. Нет новых свойств добавляются в 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, директива с 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 объем имущества, который скрывает/тени материнской сферы недвижимости с одноименным названием. Этот это наверное не то, что вы хотите/ожидаете.

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-switch

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

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

НГ-повторить

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-повторении выше каждая дочерняя область получает num свойство, которое не зависит от массива myArrayOfPrimitives:

ng-repeat with primitives

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

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

ng-repeat with objects

(я покрасил одну линию серым только так, чтобы она понятно, куда он идет.)

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

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

ng-controller

контроллеры вложенности с использованием 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, поэтому будьте осторожны с двухсторонней привязкой данных к примитивам родительской области и скрытием/затенением дочерней области родительской области свойства.
  3. 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"
    isolated scope
    для получения дополнительной информации по изоляции прицелы см. http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true - директива создает новую дочернюю область "transcluded", которая прототипически наследуется от родительской области. Закодированная и изолированная области (если таковые имеются) являются братьями и сестрами -- $parent свойство каждой области ссылается на ту же родительскую область. Когда раскрываемый и изолировать объеме существовать, изолировать объем имущества $$ / / сделать будет ссылаться на раскрываемый масштаб. Я не знаю никаких нюансов с перекрываемым объемом.
    для рисунка ниже, предположим, что та же директива, что и выше, с этим добавлением:transclude: true
    transcluded scope

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


резюме

есть четыре типа прицелы:

  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 "к дочернему прототипу, который перепишет значение свойства" примитив "в дочернем объекте и изменит" объект.одно " значение как в дочерних, так и в родительских объектах

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