Program Tip

AngularJS에서 범위 프로토 타입 / 프로토 타입 상속의 뉘앙스는 무엇입니까?

programtip 2020. 9. 27. 13:49
반응형

AngularJS에서 범위 프로토 타입 / 프로토 타입 상속의 뉘앙스는 무엇입니까?


API 참조 범위 페이지는 말한다 :

범위 상위 범위에서 상속 할 수 있습니다 .

개발자 가이드의 범위 페이지는 말한다 :

범위 (원형)는 부모 범위에서 속성을 상속합니다.

그렇다면 자식 범위는 항상 부모 범위에서 프로토 타입 적으로 상속됩니까? 예외가 있습니까? 상속 할 때 항상 일반적인 JavaScript 프로토 타입 상속입니까?


빠른 답변 :
일반적으로 자식 범위는 프로토 타입 적으로 부모 범위에서 상속되지만 항상 그런 것은 아닙니다. 이 규칙에 대한 한 가지 예외는 다음과 같은 지시문입니다 scope: { ... }. 이것은 프로토 타입 적으로 상속하지 않는 "격리"범위를 만듭니다. 이 구조는 "재사용 가능한 구성 요소"지시문을 만들 때 자주 사용됩니다.

뉘앙스와 관련하여, 범위 상속은 일반적으로 간단합니다 ... 하위 범위에서 양방향 데이터 바인딩 (즉, 양식 요소, ng-model) 이 필요할 때까지 . Ng-repeat, ng-switch 및 ng-include는 자식 범위 내에서 부모 범위 기본 요소 (예 : 숫자, 문자열, 부울)에 바인딩하려고하면 문제를 일으킬 수 있습니다 . 대부분의 사람들이 예상하는 방식으로 작동하지 않습니다. 자식 범위는 같은 이름의 부모 속성을 숨기거나 숨기는 자체 속성을 가져옵니다. 해결 방법은 다음과 같습니다.

  1. 모델의 부모에서 개체를 정의한 다음 자식에서 해당 개체의 속성을 참조합니다. parentObj.someProp
  2. $ parent.parentScopeProperty 사용 (항상 가능한 것은 아니지만 가능한 경우 1보다 쉽습니다)
  3. 부모 범위에서 함수를 정의하고 자식에서 호출 (항상 가능한 것은 아님)

새로운 AngularJS와 개발자는 그것을 깨닫지 못하고 ng-repeat, ng-switch, ng-view, ng-includeng-if이러한 지침이 관련 될 때 문제가 자주 보여줍니다 그래서 모두가 새 하위 범위를 만듭니다. ( 문제에 대한 간략한 설명은 이 예참조하십시오 .)

프리미티브와 관련된이 문제는 항상 '.'를 갖는 "모범 사례"를 따르면 쉽게 피할 수 있습니다 . 당신의 ng-models에서 – 3 분 분량의 시청. Misko는 ng-switch.

가있는 '.' 모델에서 프로토 타입 상속이 작동하는지 확인합니다. 그래서 사용

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

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


긴 대답 :

자바 스크립트 프로토 타입 상속

AngularJS 위키에도 있습니다 : https://github.com/angular/angular.js/wiki/Understanding-Scopes

먼저 프로토 타입 상속에 대해 확실히 이해하는 것이 중요합니다. 특히 서버 측 배경에서 왔고 클래스 상속에 더 익숙한 경우에는 더욱 그렇습니다. 그럼 먼저 검토해 봅시다.

parentScope에 aString, aNumber, anArray, anObject 및 aFunction 속성이 있다고 가정합니다. childScope가 prototypically parentScope에서 상속하는 경우 다음이 있습니다.

프로토 타입 상속

(공간을 절약하기 위해 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에 대해 논의 할 때 매우 중요합니다.

속성 숨기기

그런 다음 이렇게한다고 가정합니다.

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

개체 (anArray 및 anObject)가 childScope에서 찾을 수 없기 때문에 프로토 타입 체인을 참조합니다. 개체는 parentScope에 있으며 속성 값은 원래 개체에서 업데이트됩니다. childScope에 새 속성이 추가되지 않습니다. 새 개체가 생성되지 않습니다. (JavaScript에서 배열과 함수도 객체입니다.)

프로토 타입 체인을 따라

그런 다음 이렇게한다고 가정합니다.

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

프로토 타입 체인은 참조되지 않으며 자식 범위는 동일한 이름의 parentScope 개체 속성을 숨기거나 숨기는 두 개의 새 개체 속성을 가져옵니다.

더 많은 재산 숨기기

요약 :

  • childScope.propertyX를 읽고 childScope에 propertyX가있는 경우 프로토 타입 체인은 참조되지 않습니다.
  • childScope.propertyX를 설정하면 프로토 타입 체인이 참조되지 않습니다.

마지막 시나리오 :

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

먼저 childScope 속성을 삭제 한 다음 속성에 다시 액세스하려고 할 때 프로토 타입 체인을 참조합니다.

자식 속성을 제거한 후


각도 범위 상속

경쟁자 :

  • 다음은 새 범위를 만들고 프로토 타입으로 상속합니다. ng-repeat, ng-include, ng-switch, ng-controller, directive with scope: true, directive with 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 하위 범위

첫 번째 입력 텍스트 상자에 입력 (예 : "77")하면 자식 범위가 myPrimitive동일한 이름의 부모 범위 속성을 숨기거나 숨기는 범위 속성 을 가져옵니다 . 이것은 아마도 당신이 원하거나 기대하는 것이 아닐 것입니다.

프리미티브와 함께 ng-include

두 번째 입력 텍스트 상자에 입력 (예 : "99")하면 새 자식 속성이 생성되지 않습니다. tpl2.html은 모델을 객체 속성에 바인딩하기 때문에 ngModel이 객체 myObject를 찾을 때 프로토 타입 상속이 시작됩니다.-부모 범위에서 찾습니다.

개체와 함께 ng-include

모델을 기본에서 객체로 변경하지 않으려면 $ parent를 사용하도록 첫 번째 템플릿을 다시 작성할 수 있습니다.

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

이 입력 텍스트 상자에 입력 (예 : "22")하면 새 자식 속성이 생성되지 않습니다. 이제 모델이 부모 범위의 속성에 바인딩됩니다 ($ parent는 부모 범위를 참조하는 자식 범위 속성이기 때문).

$ parent와 함께 ng-include

모든 범위 (프로토 티팔 여부)에 대해 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/215945https://github.com/angular/angular.js/issues/1267참조 하세요 .

ng 스위치

ng-switch 범위 상속은 ng-include처럼 작동합니다. 따라서 부모 범위의 기본 요소에 양방향 데이터 바인딩이 필요한 경우 $ parent를 사용하거나 모델을 개체로 변경 한 다음 해당 개체의 속성에 바인딩합니다. 이렇게하면 부모 범위 속성의 자식 범위 숨기기 / 섀도 잉을 방지 할 수 있습니다.

AngularJS, bind scope of a switch-case 도 참조하십시오 .

반복

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에 대한 Angular 소스 코드는 실제로 다음과 같습니다.

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

항목이 기본 (myArrayOfPrimitives에서와 같이)이면 기본적으로 값의 복사본이 새 하위 범위 속성에 할당됩니다. 자식 범위 속성의 값을 변경 (즉, ng-model 사용하여 자식 범위 num)해도 부모 범위가 참조하는 배열은 변경 되지 않습니다 . 따라서 위의 첫 번째 ng-repeat에서 각 자식 범위는 nummyArrayOfPrimitives 배열과 독립적 인 속성을 가져 옵니다.

프리미티브로 ng-repeat

이 ng-repeat는 작동하지 않습니다 (원하거나 예상하는 것처럼). 텍스트 상자에 입력하면 하위 범위에만 표시되는 회색 상자의 값이 변경됩니다. 우리가 원하는 것은 입력이 자식 범위 기본 속성이 아니라 myArrayOfPrimitives 배열에 영향을주는 것입니다. 이를 수행하려면 모델을 객체 배열로 변경해야합니다.

따라서 항목이 개체 인 경우 복사본이 아닌 원본 개체에 대한 참조가 새 자식 범위 속성에 할당됩니다. 자식 범위 속성의 값을 변경하면 (즉, ng-model을 사용하여 obj.num) 부모 범위가 참조하는 객체 변경됩니다. 따라서 위의 두 번째 ng-repeat에는 다음이 있습니다.

개체와 반복

(나는 어디로 가는지 명확하게하기 위해 한 줄을 회색으로 칠했습니다.)

This works as expected. Typing into the textboxes changes the values in the gray boxes, which are visible to both the child and parent scopes.

See also Difficulty with ng-model, ng-repeat, and inputs and https://stackoverflow.com/a/13782671/215945

ng-controller

Nesting controllers using ng-controller results in normal prototypal inheritance, just like ng-include and ng-switch, so the same techniques apply. However, "it is considered bad form for two controllers to share information via $scope inheritance" -- http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ A service should be used to share data between controllers instead.

(If you really want to share data via controllers scope inheritance, there is nothing you need to do. The child scope will have access to all of the parent scope properties. See also Controller load order differs when loading or navigating)

directives

  1. default (scope: false) - the directive does not create a new scope, so there is no inheritance here. This is easy, but also dangerous because, e.g., a directive might think it is creating a new property on the scope, when in fact it is clobbering an existing property. This is not a good choice for writing directives that are intended as reusable components.
  2. scope: true - the directive creates a new child scope that prototypically inherits from the parent scope. If more than one directive (on the same DOM element) requests a new scope, only one new child scope is created. Since we have "normal" prototypal inheritance, this is like ng-include and ng-switch, so be wary of 2-way data binding to parent scope primitives, and child scope hiding/shadowing of parent scope properties.
  3. scope: { ... } - the directive creates a new isolate/isolated scope. It does not prototypically inherit. This is usually your best choice when creating reusable components, since the directive cannot accidentally read or modify the parent scope. However, such directives often need access to a few parent scope properties. The object hash is used to set up two-way binding (using '=') or one-way binding (using '@') between the parent scope and the isolate scope. There is also '&' to bind to parent scope expressions. So, these all create local scope properties that are derived from the parent scope. Note that attributes are used to help set up the binding -- you can't just reference parent scope property names in the object hash, you have to use an attribute. E.g., this won't work if you want to bind to parent property parentProp in the isolated scope: <div my-directive> and scope: { localProp: '@parentProp' }. An attribute must be used to specify each parent property that the directive wants to bind to: <div my-directive the-Parent-Prop=parentProp> and scope: { localProp: '@theParentProp' }.
    Isolate scope's __proto__ references Object. Isolate scope's $parent references the parent scope, so although it is isolated and doesn't inherit prototypically from the parent scope, it is still a child scope.
    For the picture below we have
    <my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"> and
    scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
    Also, assume the directive does this in its linking function: scope.someIsolateProp = "I'm isolated"
    격리 된 범위
    For more information on isolate scopes see http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true - the directive creates a new "transcluded" child scope, which prototypically inherits from the parent scope. The transcluded and the isolated scope (if any) are siblings -- the $parent property of each scope references the same parent scope. When a transcluded and an isolate scope both exist, isolate scope property $$nextSibling will reference the transcluded scope. I'm not aware of any nuances with the transcluded scope.
    For the picture below, assume the same directive as above with this addition: transclude: true
    외곽 범위

This fiddle has a showScope() function that can be used to examine an isolate and transcluded scope. See the instructions in the comments in the fiddle.


Summary

There are four types of scopes:

  1. normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true
  2. normal prototypal scope inheritance with a copy/assignment -- ng-repeat. Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.
  3. isolate scope -- directive with scope: {...}. This one is not prototypal, but '=', '@', and '&' provide a mechanism to access parent scope properties, via attributes.
  4. transcluded scope -- directive with transclude: true. This one is also normal prototypal scope inheritance, but it is also a sibling of any isolate scope.

For all scopes (prototypal or not), Angular always tracks a parent-child relationship (i.e., a hierarchy), via properties $parent and $$childHead and $$childTail.

Diagrams were generated with "*.dot" files, which are on github. Tim Caswell's "Learning JavaScript with Object Graphs" was the inspiration for using GraphViz for the diagrams.


I in no way want to compete with Mark's answer, but just wanted to highlight the piece that finally made everything click as someone new to Javascript inheritance and its prototype chain.

Only property reads search the prototype chain, not writes. So when you set

myObject.prop = '123';

It doesn't look up the chain, but when you set

myObject.myThing.prop = '123';

there's a subtle read going on within that write operation that tries to look up myThing before writing to its prop. So that's why writing to object.properties from the child gets at the parent's objects.


I would like to add an example of prototypical inheritance with javascript to @Scott Driscoll answer. We'll be using classical inheritance pattern with Object.create() which is a part of EcmaScript 5 specification.

First we create "Parent" object function

function Parent(){

}

Then add a prototype to "Parent" object function

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

Create "Child" object function

function Child(){

}

Assign child prototype (Make child prototype inherit from parent prototype)

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

Assign proper "Child" prototype constructor

Child.prototype.constructor = Child;

Add method "changeProps" to a child prototype, which will rewrite "primitive" property value in Child object and change "object.one" value both in Child and Parent objects

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

Initiate Parent (dad) and Child (son) objects.

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

Call Child (son) changeProps method

son.changeProps();

Check the results.

Parent primitive property did not change

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

Child primitive property changed (rewritten)

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

Parent and Child object.one properties changed

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

Working example here http://jsbin.com/xexurukiso/1/edit/

More info on Object.create here https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create

참고URL : https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs

반응형