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


1028

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

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

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

범위는 (시제품 적으로) 부모 범위에서 속성을 상속합니다.

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

답변:


1740

빠른 답변 :
자식 범위는 일반적으로 부모 범위에서 프로토 타입으로 상속되지만 항상 그런 것은 아닙니다. 이 규칙에 대한 한 가지 예외는 다음과 같은 지시문입니다 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가 프로토 타입으로 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에 새 속성이 추가되지 않습니다. 새로운 객체가 생성되지 않습니다. (자바 스크립트에서 배열과 함수도 객체입니다.)

프로토 타입 체인을 따르십시오

그런 다음이 작업을 수행한다고 가정합니다.

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, 지시문 포함 scope: true, 지시문 포함 transclude: true.
  • 다음은 프로토 타입을 상속하지 않는 새 범위를 만듭니다. 지시문 with 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- 포함 하위 범위

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

원시와 함께 ng-include

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

ng- 개체 포함

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

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

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

ng-include with $ parent

모든 범위 (prototypal 또는 not)에 대해 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- 스위치 범위 상속은 ng-include와 동일하게 작동합니다. 따라서 부모 범위의 프리미티브에 양방향 데이터 바인딩이 필요한 경우 $ parent를 사용하거나 모델을 객체로 변경 한 다음 해당 객체의 속성에 바인딩합니다. 이렇게하면 자식 범위 숨기기 / 부모 범위 속성이 숨겨지지 않습니다.

스위치 케이스의 바인드 범위 인 AngularJS 도 참조하십시오 .

ng-repeat

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

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

프리미티브를 사용한 ng-repeat

이 ng-repeat는 작동하지 않습니다 (원하는대로). 텍스트 상자에 입력하면 회색 상자의 값이 변경되어 하위 범위에서만 볼 수 있습니다. 우리가 원하는 것은 입력이 자식 범위 기본 속성이 아닌 myArrayOfPrimitives 배열에 영향을 미치는 것입니다. 이를 위해 모델을 객체 배열로 변경해야합니다.

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

객체로 반복

(가는 곳이 명확하도록 한 줄을 회색으로 칠했습니다.)

이것은 예상대로 작동합니다. 텍스트 상자에 입력하면 회색 상자의 값이 변경되어 자식 범위와 부모 범위 모두에 표시됩니다.

참조 겨 모델, 겨 반복하고, 입력과 난이도https://stackoverflow.com/a/13782671/215945을

ng-controller

ng-controller를 사용하는 중첩 컨트롤러는 ng-include 및 ng-switch와 마찬가지로 일반적인 프로토 타입 상속을 수행하므로 동일한 기술이 적용됩니다. 그러나 "두 개의 컨트롤러가 $ scope 상속을 통해 정보를 공유하는 것은 잘못된 형식으로 간주됩니다" -http : //onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ 서비스는 데이터를 공유하는 데 사용해야합니다 대신 컨트롤러.

(컨트롤러 범위 상속을 통해 데이터를 실제로 공유하려면 할 필요가 없습니다. 하위 범위는 모든 상위 범위 속성에 액세스 할 수 있습니다. 로드 또는 탐색시 컨트롤러로드 순서가 다름 참조 )

지시어

  1. default ( 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"
    고립 된 범위
    분리 범위에 대한 자세한 내용은 다음을 참조 http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/
  4. transclude: true-지시문은 부모 범위에서 프로토 타입으로 상속되는 새로운 "번역 된"하위 범위를 작성합니다. 변환 된 범위와 격리 된 범위 (있는 경우)는 형제입니다. 각 범위의 $ parent 속성은 동일한 부모 범위를 참조합니다. 포함 된 범위와 격리 범위가 모두 존재하면 격리 범위 속성 $$ nextSibling은 포함 된 범위를 참조합니다. 나는 포함 된 범위의 뉘앙스를 알지 못합니다.
    아래 그림의 경우이 추가 사항과 동일한 지시문을 가정하십시오.transclude: true
    막힌 범위

바이올린 에는 showScope()격리 및 포함 된 범위를 검사하는 데 사용할 수 있는 기능이 있습니다. 바이올린 주석의 지침을 참조하십시오.


요약

범위에는 네 가지 유형이 있습니다.

  1. 일반 프로토 타입 범위 상속-ng-include, ng-switch, ng-controller, 지시문 포함 scope: true
  2. 복사 / 할당-ng-repeat를 사용한 일반적인 프로토 타입 범위 상속 ng-repeat를 반복 할 때마다 새 자식 범위가 만들어지고 새 자식 범위는 항상 새 속성을 가져옵니다.
  3. 격리 범위-로 지시어 scope: {...}. 이것은 프로토 타입이 아니지만 '=', '@'및 '&'는 속성을 통해 상위 범위 속성에 액세스하는 메커니즘을 제공합니다.
  4. 포함 된 범위-로 지시어 transclude: true. 이것은 또한 일반적인 프로토 타입 범위 상속이지만 격리 범위의 형제이기도합니다.

프로토 타입이든 아니든 모든 범위에 대해 Angular는 항상 $ parent 및 $$ childHead 및 $$ childTail 속성을 통해 부모-자식 관계 (예 : 계층)를 추적합니다.

다이어그램은 에있는 "* .DOT"파일, github에 . Tim Caswell의 " 오브젝트 그래프로 JavaScript 학습 "은 다이어그램에 GraphViz를 사용하는 데 영감을주었습니다.


48
멋진 답변, 너무 오래 답변했지만 어쨌든 매우 유용합니다. 에디터가 크기를 줄이기 전에 블로그에 올리십시오.
iwein

43
AngularJS 위키 에 사본을 넣었습니다 .
Mark Rajcok

3
정정 : "스코프의 __proto__참조 오브젝트를 분리하십시오 ." 대신 "범위의 __proto__참조를 스코프 객체 분리"로 지정해야 합니다. 따라서 마지막 두 그림에서 주황색 "개체"상자는 "범위"상자 여야합니다.
Mark Rajcok

15
이 asnwer는 angularjs 안내서에 포함되어야합니다. 이것은 훨씬 더 교묘합니다 ...
Marcelo De Zen

2
위키는 당황스러워합니다. "자식 범위가 childScope에 없기 때문에 프로토 타입 체인이 참조됩니다." "childScope.propertyX를 설정하면 프로토 타입 체인을 참조하지 않습니다."라고 읽습니다. 두 번째는 조건을 의미하지만 첫 번째는 그렇지 않습니다.
Stephane

140

Mark의 답변과 경쟁하고 싶지는 않지만 Javascript 상속 및 프로토 타입 체인에 익숙하지 않은 모든 사람을 마침내 클릭하게 만든 부분을 강조하고 싶었습니다 .

속성 만 쓰기가 아닌 프로토 타입 체인을 검색합니다. 그래서 당신이 설정할 때

myObject.prop = '123';

체인을 찾지 않지만 설정하면

myObject.myThing.prop = '123';

해당 쓰기 작업 내에서 미묘한 읽기가 진행되어 소품에 쓰기 전에 myThing을 조회합니다. 따라서 자식에서 object.properties에 쓰는 것이 부모의 개체에 도달하는 이유입니다.


12
이것은 매우 간단한 개념이지만 많은 사람들이 그것을 놓친 이후 분명하지 않을 수 있습니다. 잘 넣어
moljac024 2016 년

3
훌륭한 발언. 객체 속성이 아닌 속성의 해상도는 읽기와 관련이 없지만 객체 속성의 해상도는 그렇지 않습니다.
Stephane

1
왜? 프로토 타입 체인을 올라가지 않는 속성 쓰기에 대한 동기는 무엇입니까? 미친 것 같아요
조나단

1
실제 간단한 예제를 추가하면 좋을 것입니다.
tylik

2
공지 사항이 있음을 수행 하기위한 프로토 타입 체인 검색 세터 . 아무것도 발견되지 않으면 수신자에 특성을 작성합니다.
Bergi

21

@Scott Driscoll 답변에 자바 스크립트로 프로토 타입 상속의 예를 추가하고 싶습니다. 우리는 EcmaScript 5 사양의 일부인 Object.create ()와 함께 고전적인 상속 패턴을 사용할 것입니다.

먼저 "부모"객체 함수를 만듭니다

function Parent(){

}

그런 다음 "부모"개체 함수에 프로토 타입을 추가하십시오.

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

"자식"객체 함수 생성

function Child(){

}

자식 프로토 타입 할당 (자식 프로토 타입을 부모 프로토 타입에서 상속하게 함)

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

적절한 "자식"프로토 타입 생성자 할당

Child.prototype.constructor = Child;

자식 프로토 타입에 "changeProps"메소드를 추가하면 자식 개체의 "기본"속성 값을 다시 작성하고 자식 개체와 부모 개체의 "object.one"값을 변경합니다

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

부모 (아빠) 및 자식 (아들) 개체를 시작합니다.

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

하위 (아들) 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/

Object.create에 대한 자세한 내용은 여기 https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.