คำตอบด่วน :
โดยปกติขอบเขตลูกมักจะสืบทอดจากแม่ลูก แต่ไม่เสมอไป ข้อยกเว้นประการหนึ่งสำหรับกฎนี้คือคำสั่งด้วยscope: { ... }
- สิ่งนี้จะสร้างขอบเขต "แยก" ที่ไม่ได้รับช่วงต้นแบบ โครงสร้างนี้มักจะใช้เมื่อสร้างคำสั่ง "องค์ประกอบที่ใช้ซ้ำได้"
สำหรับความแตกต่างการสืบทอดขอบเขตมักจะตรงไปตรงมา ... จนกว่าคุณจะต้องการการผูกข้อมูลแบบสองทาง (เช่นองค์ประกอบของฟอร์ม, ng-model) ในขอบเขตลูก Ng-repeat, ng-switch และ ng-include สามารถเดินทางไปหาคุณได้ถ้าคุณพยายามที่จะผูกกับดั้งเดิม (เช่นตัวเลขสตริงบูลีน) ในขอบเขตหลักจากภายในขอบเขตลูก มันไม่ทำงานอย่างที่คนส่วนใหญ่คาดหวัง ขอบเขตลูกได้รับคุณสมบัติของตนเองที่ซ่อน / เงาคุณสมบัติหลักที่มีชื่อเดียวกัน วิธีแก้ไขปัญหาของคุณคือ
- กำหนดอ็อบเจ็กต์ในพาเรนต์สำหรับโมเดลของคุณจากนั้นอ้างอิงคุณสมบัติของอ็อบเจ็กต์นั้นใน child: parentObj.someProp
- ใช้ $ parent.parentScopeProperty (ไม่เสมอไปได้ แต่ง่ายกว่า 1. ที่เป็นไปได้)
- กำหนดฟังก์ชันบนขอบเขตพาเรนต์และเรียกใช้จาก child (ไม่สามารถทำได้)
นักพัฒนา AngularJS ใหม่มักจะไม่ทราบว่าng-repeat
, ng-switch
, ng-view
, ng-include
และng-if
ทุกคนสร้างขอบเขตเด็กใหม่ดังนั้นปัญหาที่เกิดขึ้นมักจะแสดงขึ้นเมื่อสั่งเหล่านี้มีส่วนร่วม (ดูตัวอย่างนี้สำหรับภาพประกอบอย่างรวดเร็วของปัญหา)
ปัญหานี้ด้วย primitives สามารถหลีกเลี่ยงได้อย่างง่ายดายโดยทำตาม "แนวปฏิบัติที่ดีที่สุด" ซึ่งมักจะมี '.' ใน ng-models ของคุณ - รับชมได้ 3 นาที Misko ng-switch
แสดงให้เห็นถึงปัญหาที่มีผลผูกพันดั้งเดิมด้วย
มี '.' ในแบบจำลองของคุณจะทำให้มั่นใจได้ว่ามรดกต้นแบบนั้นกำลังเล่นอยู่ ดังนั้นใช้
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
คำตอบยาว :
การสร้างต้นแบบ JavaScript
วางบน AngularJS wiki ด้วย: 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 มีคุณสมบัติ X ดังนั้นเชนต้นแบบจะไม่ได้รับการพิจารณา
- ถ้าเราตั้งค่า childScope.propertyX เชนต้นแบบจะไม่ได้รับการพิจารณา
หนึ่งสถานการณ์สุดท้าย:
delete childScope.anArray
childScope.anArray[1] === 22 // true
เราลบคุณสมบัติ childScope ก่อนจากนั้นเมื่อเราพยายามเข้าถึงคุณสมบัติอีกครั้งเราจะพิจารณาถึงห่วงโซ่ต้นแบบ
การสืบทอดขอบเขตเชิงมุม
ผู้แข่งขัน:
- ต่อไปนี้จะสร้างขอบเขตใหม่และสืบทอด prototypically: NG-ซ้ำ NG-รวมถึง ng สวิทช์, NG-ควบคุมสั่งกับสั่งด้วย
scope: true
transclude: true
- ต่อไปนี้จะสร้างขอบเขตใหม่ซึ่งไม่ได้รับมรดก prototypically:
scope: { ... }
สั่งด้วย สิ่งนี้จะสร้างขอบเขต "แยก" แทน
หมายเหตุโดยค่าเริ่มต้นสั่งไม่ได้สร้างขอบเขตใหม่ - scope: false
คือเริ่มต้นคือ
NG- ได้แก่
สมมติว่าเรามีตัวควบคุมของเรา:
$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 จะสร้างขอบเขตลูกใหม่ซึ่งแม่แบบสืบทอดมาจากขอบเขตแม่
การพิมพ์ (พูด "77") ลงในกล่องข้อความอินพุตแรกทำให้ขอบเขตลูกได้รับmyPrimitive
คุณสมบัติขอบเขตใหม่ที่ซ่อน / เงาคุณสมบัติขอบเขตของพาเรนต์ที่มีชื่อเดียวกัน นี่อาจไม่ใช่สิ่งที่คุณต้องการ / คาดหวัง
การพิมพ์ (พูด "99") ลงในกล่องข้อความอินพุตที่สองไม่ส่งผลให้คุณสมบัติลูกใหม่ เนื่องจาก tpl2.html ผูกโมเดลเข้ากับคุณสมบัติของวัตถุการสืบทอดต้นแบบจะเริ่มขึ้นเมื่อ ngModel ค้นหา object myObject - พบในขอบเขตของพาเรนต์
เราสามารถเขียนเทมเพลตแรกเพื่อใช้ $ parent ถ้าเราไม่ต้องการเปลี่ยนโมเดลจากแบบดั้งเดิมเป็นวัตถุ:
<input ng-model="$parent.myPrimitive">
การพิมพ์ (พูด "22") ลงในกล่องข้อความอินพุตนี้ไม่ได้ส่งผลให้คุณสมบัติลูกใหม่ ตอนนี้โมเดลถูกโยงกับคุณสมบัติของขอบเขตพาเรนต์ (เนื่องจาก $ 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 ดังนั้นหากคุณต้องการผูกข้อมูลแบบ 2 ทางกับขอบเขตดั้งเดิมในขอบเขตพาเรนต์ให้ใช้ $ 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 ซ้ำสร้างขอบเขตใหม่ซึ่ง prototypically สืบทอดจากขอบเขตแม่แต่ก็ยังกำหนดมูลค่าของรายการไปยังสถานที่ให้บริการใหม่ในขอบเขตเด็กใหม่ (ชื่อของคุณสมบัติใหม่คือชื่อของตัวแปรลูป) นี่คือสิ่งที่ซอร์สโค้ด Angular สำหรับ ng-repeat จริงคือ:
childScope = scope.$new(); // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value; // creates a new childScope property
หากรายการนั้นเป็นแบบดั้งเดิม (เช่นใน myArrayOfPrimitives) โดยพื้นฐานแล้วสำเนาของค่าจะถูกกำหนดให้กับคุณสมบัติขอบเขตลูกใหม่ การเปลี่ยนค่าของคุณสมบัติขอบเขตขอบเขตลูก (เช่นใช้ ng-model ขอบเขตขอบเขตลูกnum
) จะไม่เปลี่ยนอาร์เรย์ที่อ้างอิงขอบเขตขอบเขตหลัก ดังนั้นในการทำซ้ำ ng แรกข้างต้นแต่ละขอบเขตของลูกจะได้รับnum
คุณสมบัติที่ไม่ขึ้นอยู่กับอาร์เรย์ myArrayOfPrimitives:
การทำซ้ำ ng นี้จะไม่ทำงาน (เช่นคุณต้องการ / คาดหวังว่าจะ) การพิมพ์ลงในกล่องข้อความจะเปลี่ยนค่าในกล่องสีเทาซึ่งจะปรากฏเฉพาะในขอบเขตลูก สิ่งที่เราต้องการสำหรับอินพุตจะมีผลกับอาร์เรย์ myArrayOfPrimitives ไม่ใช่คุณสมบัติดั้งเดิมของขอบเขตลูก เพื่อให้บรรลุสิ่งนี้เราจำเป็นต้องเปลี่ยนรูปแบบให้เป็นอาร์เรย์ของวัตถุ
ดังนั้นถ้ารายการเป็นวัตถุการอ้างอิงไปยังวัตถุต้นฉบับ (ไม่ใช่สำเนา) จะถูกกำหนดให้กับคุณสมบัติขอบเขตลูกใหม่ การเปลี่ยนค่าของคุณสมบัติขอบเขตลูก (เช่นการใช้ ng-model ดังนั้นobj.num
) จะเปลี่ยนวัตถุที่การอ้างอิงขอบเขตหลัก ดังนั้นในซ้ำ ng ที่สองข้างต้นเรามี:
(ฉันวาดสีเทาหนึ่งบรรทัดเพื่อให้ชัดเจนว่ากำลังจะเกิดอะไรขึ้น)
ทำงานได้ตามที่คาดไว้ การพิมพ์ลงในกล่องข้อความจะเปลี่ยนค่าในกล่องสีเทาซึ่งสามารถมองเห็นได้ทั้งขอบเขตลูกและขอบเขต
ดูความยากลำบากด้วย ng-model, ng-repeat และอินพุตและ
https://stackoverflow.com/a/13782671/215945
NG-ควบคุม
ตัวควบคุมการซ้อนที่ใช้ ng-controller ส่งผลให้เกิดการสืบทอดต้นแบบปกติเช่นเดียวกับ ng-include และ ng-switch ดังนั้นจึงใช้เทคนิคเดียวกัน อย่างไรก็ตาม "มันถือว่าเป็นรูปแบบที่ไม่ดีสำหรับผู้ควบคุมสองคนที่จะแบ่งปันข้อมูลผ่านการสืบทอด $ scope" - http://onehungrymind.com/angularjs-sticky-notes-ptes-pt-1-architecture/
บริการควรใช้ข้อมูลร่วมกันระหว่าง ตัวควบคุมแทน
(หากคุณต้องการแบ่งปันข้อมูลผ่านการสืบทอดขอบเขตตัวควบคุมจริงๆคุณไม่จำเป็นต้องทำอะไรขอบเขตลูกจะสามารถเข้าถึงคุณสมบัติขอบเขตหลักทั้งหมดดูลำดับการโหลดตัวควบคุมต่างกันเมื่อโหลดหรือนำทาง )
สั่ง
- default (
scope: false
) - คำสั่งไม่ได้สร้างขอบเขตใหม่ดังนั้นจึงไม่มีการสืบทอดที่นี่ สิ่งนี้เป็นเรื่องง่าย แต่ก็เป็นอันตรายเพราะเช่นคำสั่งอาจคิดว่ามันกำลังสร้างคุณสมบัติใหม่บนขอบเขตเมื่อในความเป็นจริงมันเป็นการอุดตันทรัพย์สินที่มีอยู่ นี่ไม่ใช่ตัวเลือกที่ดีสำหรับการเขียนคำสั่งที่มีวัตถุประสงค์เพื่อใช้เป็นส่วนประกอบ
scope: true
- คำสั่งสร้างขอบเขตลูกใหม่ที่ต้นแบบสืบทอดมาจากขอบเขตแม่ หากมีมากกว่าหนึ่งคำสั่ง (ในองค์ประกอบ DOM เดียวกัน) ร้องขอขอบเขตใหม่จะมีการสร้างขอบเขตลูกใหม่เพียงขอบเขตเดียว เนื่องจากเรามีการสืบทอดต้นแบบ "ปกติ" นี่เป็นเหมือน ng-include และ ng-switch ดังนั้นให้ระมัดระวังการเชื่อมโยงข้อมูลแบบสองทางกับขอบเขตหลักของขอบเขตหลักและซ่อนขอบเขตลูกของคุณสมบัติขอบเขตหลัก
scope: { ... }
- คำสั่งสร้างขอบเขตแยก / แยกใหม่ มันไม่ได้รับมรดกต้นแบบ นี่เป็นทางเลือกที่ดีที่สุดของคุณเมื่อสร้างส่วนประกอบที่ใช้ซ้ำได้เนื่องจากคำสั่งไม่สามารถอ่านหรือแก้ไขขอบเขตพาเรนต์ อย่างไรก็ตามคำสั่งดังกล่าวมักต้องการการเข้าถึงคุณสมบัติขอบเขตพาเรนต์ไม่กี่รายการ แฮชของวัตถุใช้เพื่อตั้งค่าการรวมสองทาง (โดยใช้ '=') หรือการเชื่อมโยงทางเดียว (โดยใช้ '@') ระหว่างขอบเขตหลักและขอบเขตแยก นอกจากนี้ยังมี '&' เพื่อเชื่อมโยงกับนิพจน์ขอบเขตพาเรนต์ ดังนั้นสิ่งเหล่านี้ทั้งหมดสร้างคุณสมบัติขอบเขตภายในที่ได้รับมาจากขอบเขตหลัก โปรดทราบว่ามีการใช้แอททริบิวเพื่อช่วยในการตั้งค่าการเชื่อมโยง - คุณไม่สามารถอ้างอิงชื่อคุณสมบัติของขอบเขตพาเรนต์ในแฮชของวัตถุได้คุณต้องใช้แอททริบิวต์ เช่นนี้จะไม่ทำงานหากคุณต้องการผูกเข้ากับคุณสมบัติผู้ปกครองparentProp
ในขอบเขตที่แยก: และ<div my-directive>
scope: { localProp: '@parentProp' }
แอตทริบิวต์จะต้องใช้ในการระบุแต่ละสถานที่ให้บริการผู้ปกครองที่สั่งต้องการที่จะผูกกับ: และ
<div my-directive the-Parent-Prop=parentProp>
แยกวัตถุอ้างอิงขอบเขต แยก $ parent ขอบเขตอ้างอิงถึงขอบเขตพาเรนต์ดังนั้นแม้ว่าจะแยกได้และไม่สืบทอดต้นแบบจากขอบเขตพาเรนต์ แต่ก็ยังคงเป็นขอบเขตลูก
สำหรับภาพด้านล่างเรามี
และ
สมมติว่าคำสั่งทำเช่นนี้ในฟังก์ชั่นการเชื่อมโยง: สำหรับข้อมูลเพิ่มเติมเกี่ยวกับขอบเขตแยกดูได้ที่http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/scope: { localProp: '@theParentProp' }
__proto__
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2">
scope: { interpolatedProp: '@interpolated', twowayBindingProp: '=twowayBinding' }
scope.someIsolateProp = "I'm isolated"
transclude: true
- คำสั่งจะสร้างขอบเขตย่อย "transcluded" ใหม่ซึ่งสืบทอดมาจากต้นแบบขอบเขต transcluded และ scope isolated (ถ้ามี) คือพี่น้อง - ทรัพย์สิน $ parent ของแต่ละขอบเขตอ้างอิงถึงขอบเขต parent เดียวกัน เมื่อทั้งสอง transcluded และ isolate scope อยู่แล้วคุณสมบัติ isolate ของขอบเขต $$ nextSibling จะอ้างอิงขอบเขตของ transcluded ฉันไม่ทราบถึงความแตกต่างใด ๆ กับขอบเขต transcluded
สำหรับภาพด้านล่างให้ถือเป็นคำสั่งเดียวกันกับข้างต้นด้วยการเพิ่มนี้:transclude: true
ซอนี้มีshowScope()
ฟังก์ชั่นที่สามารถใช้ในการตรวจสอบขอบเขตไอโซเลตและ transcluded ดูคำแนะนำในข้อคิดเห็นในซอ
สรุป
ขอบเขตมีสี่ประเภท:
- การถ่ายทอดขอบเขตต้นแบบต้นแบบปกติ - ng-include, ng-switch, ng-controller, directive with
scope: true
- การสืบทอดขอบเขตต้นแบบต้นแบบปกติพร้อมสำเนา / การมอบหมาย - ng-repeat การวนซ้ำของ ng-repeat แต่ละครั้งจะสร้างขอบเขตลูกใหม่และขอบเขตลูกใหม่นั้นจะได้รับคุณสมบัติใหม่เสมอ
- ขอบเขตแยก -
scope: {...}
สั่งด้วย อันนี้ไม่ใช่ต้นแบบ แต่ '=', '@' และ '&' ให้กลไกในการเข้าถึงคุณสมบัติขอบเขตหลักผ่านทางคุณสมบัติ
- ขอบเขตเรียก -
transclude: true
สั่งด้วย อันนี้ก็คือการสืบทอดขอบเขตต้นแบบต้นแบบปกติ แต่ก็ยังเป็นพี่น้องของขอบเขตแยกใด ๆ
สำหรับขอบเขตทั้งหมด (ต้นแบบหรือไม่) Angular จะติดตามความสัมพันธ์ระหว่างผู้ปกครองกับลูก (เช่นลำดับชั้น) เสมอผ่านคุณสมบัติ $ parent และ $$ childHead และ $$ childTail
สร้างไดอะแกรมด้วย Graphviz"* .dot ไฟล์" ซึ่งอยู่บนGitHub " การเรียนรู้จาวาสคริปต์ด้วยกราฟวัตถุ " ของ Tim Caswell เป็นแรงบันดาลใจในการใช้ GraphViz สำหรับไดอะแกรม