'this' vs $ scope ในตัวควบคุม AngularJS


1026

ในส่วน"สร้างส่วนประกอบ" ในหน้าแรกของ AngularJSมีตัวอย่างนี้:

controller: function($scope, $element) {
  var panes = $scope.panes = [];
  $scope.select = function(pane) {
    angular.forEach(panes, function(pane) {
      pane.selected = false;
    });
    pane.selected = true;
  }
  this.addPane = function(pane) {
    if (panes.length == 0) $scope.select(pane);
    panes.push(pane);
  }
}

แจ้งให้ทราบว่าselectวิธีการที่จะถูกเพิ่ม$scopeแต่วิธีการที่จะถูกเพิ่มaddPane thisหากฉันเปลี่ยน$scope.addPaneเป็นรหัสจะแตก

เอกสารบอกว่าจริงๆแล้วมีความแตกต่าง แต่ไม่ได้พูดถึงความแตกต่าง:

รุ่นก่อนหน้าของ Angular (pre 1.0 RC) อนุญาตให้คุณใช้วิธีการthisสลับแทนกันได้$scopeแต่นี่ไม่ใช่กรณีอีกต่อไป ด้านในของวิธีการที่กำหนดไว้ในขอบเขตthisและ$scopeสามารถใช้แทนกันได้ (ชุดเชิงมุมthisเป็น$scope) แต่ไม่ได้อยู่ในคอนสตรัคเตอร์คอนโทรลเลอร์ของคุณ

อย่างไรthisและ$scopeการทำงานในการควบคุม AngularJS?


ฉันพบว่ามันสับสนเช่นกัน เมื่อมุมมองระบุตัวควบคุม (เช่น ng-controller = '... '), $ scope ที่เกี่ยวข้องกับตัวควบคุมนั้นดูเหมือนจะมาพร้อมกับมันเพราะมุมมองสามารถเข้าถึงคุณสมบัติ $ scope แต่เมื่อ directive 'ต้องการตัวควบคุมอื่น (แล้วใช้มันในฟังก์ชั่นการเชื่อมโยง) ขอบเขต $ ที่เกี่ยวข้องกับตัวควบคุมอื่นนั้นไม่ได้มาพร้อมกับมัน?
Mark Rajcok

คำพูดที่สับสนเกี่ยวกับ "รุ่นก่อนหน้า ... " ถูกลบโดยตอนนี้หรือไม่ ถ้าเป็นเช่นนั้นอาจมีการอัปเดต
Dmitri Zaitsev

สำหรับการทดสอบหน่วยหากคุณใช้ 'this' แทน '$ scope' คุณจะไม่สามารถฉีดคอนโทรลเลอร์ด้วยขอบเขตที่จำลองไว้ดังนั้นคุณจึงไม่สามารถทำการทดสอบหน่วยได้ ฉันไม่คิดว่ามันเป็นวิธีปฏิบัติที่ดีในการใช้ 'นี้'
abentan

คำตอบ:


999

"ทำงานอย่างไรthisและ$scopeทำงานในคอนโทรลเลอร์ของ AngularJS ได้อย่างไร"

คำตอบสั้น ๆ :

  • this
    • เมื่อเรียกใช้ฟังก์ชั่นคอนสตรัคเตอร์คอนโทรลเลอร์จะthisเป็นคอนโทรลเลอร์
    • เมื่อฟังก์ชั่นที่กำหนดไว้ใน$scopeวัตถุถูกเรียกthisเป็น "ขอบเขตที่มีผลบังคับใช้เมื่อมีการเรียกใช้ฟังก์ชัน" สิ่งนี้อาจ (หรืออาจไม่ใช่!) เป็น$scopeฟังก์ชันที่กำหนดไว้ ดังนั้นภายในฟังก์ชั่นthisและ$scopeอาจไม่เหมือนกัน
  • $scope
    • ทุกตัวควบคุมมี$scopeวัตถุที่เกี่ยวข้อง
    • ตัวควบคุม (คอนสตรัค) ฟังก์ชั่นเป็นผู้รับผิดชอบสำหรับการตั้งค่าคุณสมบัติรูปแบบและฟังก์ชั่น / $scopeพฤติกรรมที่เกี่ยวข้อง
    • เฉพาะวิธีการที่กำหนดไว้ใน$scopeวัตถุนี้(และวัตถุขอบเขตของแม่ถ้ามรดกต้นแบบอยู่ในการเล่น) สามารถเข้าถึงได้จาก HTML / มุมมอง เช่นจากng-clickตัวกรอง ฯลฯ

คำตอบยาว :

ฟังก์ชั่นควบคุมเป็นฟังก์ชั่นตัวสร้างจาวาสคริปต์ เมื่อฟังก์ชันตัวสร้างดำเนินการ (เช่นเมื่อโหลดมุมมอง), this(กล่าวคือ "บริบทฟังก์ชัน") ถูกตั้งค่าเป็นวัตถุตัวควบคุม ดังนั้นในฟังก์ชั่นตัวควบคุม "แท็บ" เมื่อสร้างฟังก์ชั่น addPane

this.addPane = function(pane) { ... }

มันถูกสร้างขึ้นบนวัตถุตัวควบคุมไม่ใช่ในขอบเขต $ มุมมองไม่สามารถเห็นฟังก์ชั่น addPane - พวกเขามีการเข้าถึงฟังก์ชั่นที่กำหนดไว้ใน $ scope เท่านั้น กล่าวอีกนัยหนึ่งใน HTML สิ่งนี้จะไม่ทำงาน:

<a ng-click="addPane(newPane)">won't work</a>

หลังจากฟังก์ชั่นตัวควบคุม "แท็บ" ของตัวควบคุมดำเนินการเรามีดังต่อไปนี้:

หลังจากฟังก์ชั่นตัวควบคุมแท็บตัวสร้าง

เส้นสีดำประบ่งชี้มรดก prototypal - ขอบเขตแยก prototypically สืบทอดจากขอบเขต (ไม่ได้สืบทอดมาจากขอบเขตที่มีผลบังคับใช้เมื่อพบคำสั่งใน HTML)

ตอนนี้ฟังก์ชั่นการเชื่อมโยงของคำสั่งบานหน้าต่างต้องการสื่อสารกับคำสั่งของแท็บ (ซึ่งหมายความว่าจำเป็นต้องส่งผลกระทบต่อแท็บแยกขอบเขต $ ในบางวิธี) สามารถใช้เหตุการณ์ได้ แต่กลไกอีกอย่างหนึ่งคือการให้บานหน้าต่างrequireควบคุมตัวควบคุมแท็บ (ดูเหมือนจะไม่มีกลไกสำหรับคำสั่งบานหน้าต่างเพื่อrequireขอบเขต $ แท็บ)

ดังนั้นสิ่งนี้จึงเป็นคำถาม: หากเรามีสิทธิ์เข้าถึงตัวควบคุมแท็บเราจะเข้าถึงแท็บแยกขอบเขต $ ได้อย่างไร (ซึ่งเป็นสิ่งที่เราต้องการจริงๆ)?

เส้นประสีแดงคือคำตอบ addPane () "ขอบเขต" ของฟังก์ชัน (ฉันอ้างถึงขอบเขต / ปิดฟังก์ชันของ JavaScript ที่นี่) ให้การเข้าถึงฟังก์ชันกับแท็บแยก $ scope นั่นคือ addPane () มีการเข้าถึง "tabs IsolateScope" ในแผนภาพด้านบนเนื่องจากการปิดที่สร้างขึ้นเมื่อกำหนด addPane () (ถ้าเราแทนที่จะกำหนด addPane () บนวัตถุ $ scope แท็บคำสั่งบานหน้าต่างจะไม่สามารถเข้าถึงฟังก์ชั่นนี้และด้วยเหตุนี้มันจะไม่มีวิธีสื่อสารกับแท็บ $ scope)

ในการตอบคำถามอื่น ๆ ของคุณhow does $scope work in controllers?:

ภายในฟังก์ชั่นที่กำหนดไว้ใน $ scope thisถูกตั้งค่าเป็น "ขอบเขต $ มีผลบังคับใช้ที่ / เมื่อฟังก์ชันถูกเรียกว่า" สมมติว่าเรามี HTML ดังต่อไปนี้:

<div ng-controller="ParentCtrl">
   <a ng-click="logThisAndScope()">log "this" and $scope</a> - parent scope
   <div ng-controller="ChildCtrl">
      <a ng-click="logThisAndScope()">log "this" and $scope</a> - child scope
   </div>
</div>

และสิ่งที่ParentCtrl(เท่านั้น) มี

$scope.logThisAndScope = function() {
    console.log(this, $scope)
}

คลิกที่ลิงค์แรกจะแสดงให้เห็นว่าthisและ$scopeจะเหมือนกันตั้งแต่ " ขอบเขตในผลเมื่อฟังก์ชั่นที่ถูกเรียกว่า " ParentCtrlขอบเขตที่เกี่ยวข้องกับ

คลิกที่ลิงค์ที่สองจะเปิดเผยthisและ$scopeมีไม่เหมือนกันตั้งแต่ " ขอบเขตในผลเมื่อฟังก์ชั่นที่ถูกเรียกว่า " ChildCtrlขอบเขตที่เกี่ยวข้องกับ ดังนั้นที่นี่thisมีการตั้งค่า'sChildCtrl $scopeข้างในวิธี$scopeนี้ยังคงParentCtrlเป็นขอบเขตของ $

ซอ

ฉันพยายามที่จะไม่ใช้thisภายในฟังก์ชั่นที่กำหนดไว้ใน $ scope เพราะมันจะทำให้เกิดความสับสนซึ่ง $ scope ได้รับผลกระทบโดยเฉพาะอย่างยิ่งเมื่อพิจารณาว่า ng-repeat, ng-include, ng-switch และคำสั่งทั้งหมดสามารถสร้างขอบเขตลูกของตนเอง


6
@tamakisquare ฉันเชื่อว่าข้อความที่เป็นตัวหนาที่คุณอ้างถึงนำไปใช้เมื่อฟังก์ชันตัวสร้างคอนโทรลเลอร์เรียกว่า - เช่นเมื่อคอนโทรลเลอร์ถูกสร้างขึ้น = เกี่ยวข้องกับขอบเขต $ มันจะไม่นำมาใช้ในภายหลังเมื่อรหัส JavaScript ที่กำหนดเองเรียกวิธีการที่กำหนดไว้ในวัตถุ $ scope
Mark Rajcok

79
โปรดทราบว่าขณะนี้เป็นไปได้ที่จะเรียกใช้ฟังก์ชัน addPane () โดยตรงในเทมเพลตโดยการตั้งชื่อคอนโทรลเลอร์: "MyController as myctrl" จากนั้น myctrl.addPane () ดูdocs.angularjs.org/guide/concepts#controller
Christophe Augier

81
ความซับซ้อนโดยธรรมชาติมากเกินไป
Inanc Gumus

11
นี่เป็นคำตอบที่ให้ข้อมูลมาก แต่เมื่อฉันกลับมาพร้อมกับปัญหาที่เกิดขึ้นจริง ( วิธีเรียกใช้ $ scope $ ใช้ () ในวิธีการควบคุมที่กำหนดโดยใช้ 'this' ) ฉันไม่สามารถทำงานได้ ดังนั้นในขณะนี้ยังคงเป็นคำตอบที่มีประโยชน์ฉันกำลังค้นหา "ความซับซ้อนโดยธรรมชาติ" ยุ่งเหยิง
dumbledad

11
Javascript - เชือกจำนวนมาก [เพื่อแขวนด้วยตัวเอง]
AlikElzin-kilaka

55

เหตุผลที่ 'addPane' ถูกกำหนดให้กับสิ่งนี้เป็นเพราะ<pane>คำสั่ง

paneสั่งไม่require: '^tabs'ซึ่งทำให้แท็บวัตถุควบคุมจากคำสั่งผู้ปกครองในการทำงานการเชื่อมโยง

addPaneถูกกำหนดให้thisเพื่อให้paneฟังก์ชันลิงก์สามารถดูได้ จากนั้นในpaneฟังก์ชั่นลิงค์addPaneเป็นเพียงคุณสมบัติของtabsคอนโทรลเลอร์และมันเป็นเพียง tabsControllerObject.addPane ดังนั้นฟังก์ชั่นการเชื่อมโยงคำสั่งของบานหน้าต่างสามารถเข้าถึงวัตถุตัวควบคุมแท็บและเข้าถึงวิธีการ addPane

ฉันหวังว่าคำอธิบายของฉันจะชัดเจนเพียงพอ .. มันยากที่จะอธิบาย


3
ขอบคุณสำหรับคำอธิบาย เอกสารทำให้ดูเหมือนว่าคอนโทรลเลอร์เป็นเพียงฟังก์ชันที่กำหนดขอบเขต ทำไมคอนโทรลเลอร์ได้รับการปฏิบัติเหมือนเป็นวัตถุหากการกระทำทั้งหมดเกิดขึ้นในขอบเขต? ทำไมไม่เพียงแค่ส่งผ่านขอบเขตพาเรนต์ไปยังฟังก์ชันลิงก์ แก้ไข: เพื่อวลีที่ดีกว่าคำถามนี้หากวิธีการควบคุมและวิธีขอบเขตทั้งสองทำงานในโครงสร้างข้อมูลเดียวกัน (ขอบเขต) ทำไมไม่ใส่ไว้ในที่เดียว?
Alexei Boronine

ดูเหมือนว่าขอบเขตหลักจะไม่ถูกส่งผ่านไปยัง lnk func เนื่องจากมีความปรารถนาที่จะสนับสนุน "ส่วนประกอบที่นำมาใช้ซ้ำได้ซึ่งไม่ควรอ่านหรือแก้ไขข้อมูลในขอบเขตหลักโดยไม่ตั้งใจ" แต่ถ้าคำสั่งต้องการ / ต้องการอ่านหรือแก้ไขข้อมูลจำเพาะบางอย่างในขอบเขตพาเรนต์ (เช่นคำสั่ง 'บานหน้าต่าง') มันต้องใช้ความพยายาม: 'ต้องการ' ตัวควบคุมที่ขอบเขตของพาเรนต์ที่ต้องการนั้นกำหนด วิธีการในตัวควบคุมนั้น (ใช้ 'this' ไม่ใช่ $ scope) เพื่อเข้าถึงข้อมูลเฉพาะ เนื่องจากขอบเขตผู้ปกครองที่ต้องการไม่ได้ถูกฉีดเข้าไปใน lnk func ฉันคิดว่านี่เป็นวิธีเดียวที่จะทำได้
Mark Rajcok

1
เดี๋ยวก่อนมันง่ายกว่าที่จะแก้ไขขอบเขตของคำสั่ง คุณสามารถใช้ฟังก์ชั่นลิงค์jsfiddle.net/TuNyj
Andrew Joslin

3
ขอบคุณ @Andy สำหรับซอ ในซอของคุณคำสั่งไม่ได้สร้างขอบเขตใหม่ดังนั้นฉันสามารถดูว่าฟังก์ชั่นลิงค์สามารถเข้าถึงขอบเขตของคอนโทรลเลอร์ได้โดยตรงที่นี่ (เนื่องจากมีเพียงขอบเขตเดียว) คำสั่งแท็บและบานหน้าต่างใช้ขอบเขตแยก (เช่นขอบเขตลูกใหม่จะถูกสร้างขึ้นที่ไม่ได้รับมรดกจากต้นแบบขอบเขต) สำหรับกรณีขอบเขต isolate ดูเหมือนว่าการกำหนดวิธีการในตัวควบคุม (ใช้ 'นี้') เป็นวิธีเดียวที่จะช่วยให้คำสั่งอื่นได้รับการเข้าถึง (ทางอ้อม) ไปยังขอบเขต (แยก) อื่น ๆ
Mark Rajcok

27

ฉันเพิ่งอ่านคำอธิบายที่น่าสนใจเกี่ยวกับความแตกต่างระหว่างทั้งสองและความชอบที่เพิ่มขึ้นเพื่อแนบโมเดลเข้ากับคอนโทรลเลอร์และนามแฝงของคอนโทรลเลอร์เพื่อผูกโมเดลเข้ากับมุมมอง http://toddmotto.com/digging-into-angulars-controller-as-syntax/เป็นบทความ
เขาไม่ได้พูดถึงมัน แต่เมื่อกำหนดคำสั่งถ้าคุณต้องการแบ่งปันบางสิ่งระหว่างคำสั่งหลายคำสั่งและไม่ต้องการบริการ

$scopeบริการให้ความอุดมสมบูรณ์ของสิ่งที่มีประโยชน์$watchเป็นที่ชัดเจนที่สุด แต่ถ้าสิ่งที่คุณต้องผูกข้อมูลไปยังมุมมองโดยใช้ตัวควบคุมธรรมดาและ 'ตัวควบคุมเป็น' ในแม่แบบที่ดีและดีกว่าเนื้อหา


20

ฉันแนะนำให้คุณอ่านโพสต์ต่อไปนี้: AngularJS: "Controller as" หรือ "$ scope"?

มันอธิบายถึงข้อดีของการใช้ "Controller as" เป็นอย่างดีในการแสดงตัวแปรเหนือ "$ scope"

ฉันรู้ว่าคุณถามถึงวิธีการเฉพาะและไม่ใช่ตัวแปร แต่ฉันคิดว่าดีกว่าที่จะใช้เทคนิคเดียวและสอดคล้องกับมัน

ดังนั้นสำหรับความเห็นของฉันเนื่องจากปัญหาของตัวแปรที่กล่าวถึงในโพสต์ควรใช้เทคนิค "Controller as" และใช้กับวิธีการ


16

ในหลักสูตรนี้ ( https://www.codeschool.com/courses/shaping-up-with-angular-js ) พวกเขาอธิบายวิธีใช้ "สิ่งนี้" และสิ่งอื่น ๆ อีกมากมาย

ถ้าคุณเพิ่มวิธีการควบคุมผ่านวิธี "นี้" คุณจะต้องเรียกมันในมุมมองที่มีชื่อ "จุด" ของตัวควบคุมหรือคุณสมบัติของคุณ

ตัวอย่างเช่นการใช้คอนโทรลเลอร์ของคุณในมุมมองคุณอาจมีรหัสดังนี้:

    <div data-ng-controller="YourController as aliasOfYourController">

       Your first pane is {{aliasOfYourController.panes[0]}}

    </div>

6
หลังจากผ่านหลักสูตรฉันก็สับสนทันทีโดยใช้รหัส$scopeดังนั้นขอบคุณที่พูดถึงมัน
Matt Montag

16
หลักสูตรนั้นไม่ได้กล่าวถึง $ scope เลยพวกเขาใช้asและthisดังนั้นจะช่วยอธิบายความแตกต่างได้อย่างไร
dumbledad

10
การสัมผัสครั้งแรกของฉันกับ Angular นั้นมาจากหลักสูตรที่กล่าวถึงและอย่างที่$scopeไม่เคยมีใครรู้จักฉันเรียนรู้ที่จะใช้thisตัวควบคุมเพียงอย่างเดียว ปัญหาคือเมื่อคุณเริ่มที่จะมีสัญญาในตัวควบคุมของคุณคุณมีปัญหาการอ้างอิงจำนวนมากthisและต้องเริ่มทำสิ่งต่าง ๆ เช่นvar me = thisการอ้างอิงแบบจำลองthisจากภายในฟังก์ชั่นการส่งคืนสัญญา ดังนั้นเนื่องจากการที่ฉันยังคงสับสนมากเกี่ยวกับวิธีการที่ฉันควรใช้หรือ$scope this
Bruno Finger

@BrunoFinger น่าเสียดายที่คุณต้องการvar me = thisหรือ.bind(this)เมื่อใดก็ตามที่คุณทำสัญญาหรือสิ่งปิดอื่น ๆ ที่หนัก มันไม่มีส่วนเกี่ยวข้องกับ Angular
Dzmitry Lazerka

1
สิ่งสำคัญคือการรู้ว่าng-controller="MyCtrl as MC"เทียบเท่ากับการใส่$scope.MC = thisตัวควบคุม - มันกำหนดอินสแตนซ์ (นี่) ของ MyCtrl บนขอบเขตสำหรับใช้ในแม่แบบผ่านทาง{{ MC.foo }}
William B

3

เวอร์ชันก่อนหน้าของ Angular (pre 1.0 RC) อนุญาตให้คุณใช้วิธีนี้แทนกันได้ด้วยวิธี $ scope แต่นี่ไม่ใช่กรณีอีกต่อไป ด้านในของวิธีการที่กำหนดไว้ในขอบเขตนี้และ $ scope สามารถใช้แทนกันได้ (เชิงมุมตั้งค่านี้เป็น $ scope) แต่ไม่ได้อยู่ใน Constructor คอนโทรลเลอร์ของคุณ

เพื่อนำพฤติกรรมนี้กลับมา (ไม่มีใครรู้ว่าทำไมมันถึงเปลี่ยนไป) คุณสามารถเพิ่ม:

return angular.extend($scope, this);

ในตอนท้ายของฟังก์ชั่นตัวควบคุมของคุณ (โดยมีเงื่อนไขว่า $ scope ถูกฉีดเข้ากับฟังก์ชั่นตัวควบคุมนี้)

สิ่งนี้มีผลดีในการเข้าถึงขอบเขตพาเรนต์ผ่านออบเจ็กต์ตัวควบคุมที่คุณสามารถทำได้ด้วยลูก require: '^myParentDirective'


7
บทความนี้ให้คำอธิบายที่ดีว่าทำไมถึงเป็นเช่นนี้และ $ scope ต่างกัน
Robert Martin

1

$ scope มีความแตกต่าง 'this' จากนั้นคอนโทรลเลอร์ 'this'.This ดังนั้นถ้าคุณใส่ console.log (นี่) ภายในคอนโทรลเลอร์มันจะให้วัตถุ (คอนโทรลเลอร์) และ this.addPane () เพิ่ม addPane Method ให้กับ Object Object แต่ขอบเขต $ มีขอบเขตที่แตกต่างกันและวิธีการทั้งหมดในขอบเขตจำเป็นต้องได้รับการรับรองโดย $ scope.methodName () this.methodName()ภายในตัวควบคุมหมายถึงการเพิ่ม methos ภายในวัตถุตัวควบคุม $scope.functionName()อยู่ใน HTML และภายใน

$scope.functionName(){
    this.name="Name";
    //or
    $scope.myname="myname"//are same}

วางรหัสนี้ในโปรแกรมแก้ไขของคุณและเปิดคอนโซลเพื่อดู ...

 <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>this $sope vs controller</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.7/angular.min.js"></script>
    <script>
        var app=angular.module("myApp",[]);
app.controller("ctrlExample",function($scope){
          console.log("ctrl 'this'",this);
          //this(object) of controller different then $scope
          $scope.firstName="Andy";
          $scope.lastName="Bot";
          this.nickName="ABot";
          this.controllerMethod=function(){

            console.log("controllerMethod ",this);
          }
          $scope.show=function(){
              console.log("$scope 'this",this);
              //this of $scope
              $scope.message="Welcome User";
          }

        });
</script>
</head>
<body ng-app="myApp" >
<div ng-controller="ctrlExample">
       Comming From $SCOPE :{{firstName}}
       <br><br>
       Comming from $SCOPE:{{lastName}}
       <br><br>
       Should Come From Controller:{{nickName}}
       <p>
            Blank nickName is because nickName is attached to 
           'this' of controller.
       </p>

       <br><br>
       <button ng-click="controllerMethod()">Controller Method</button>

       <br><br>
       <button ng-click="show()">Show</button>
       <p>{{message}}</p>

   </div>

</body>
</html>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.