'this' vs $ scope trong bộ điều khiển AngularJS


1027

Trong phần "Tạo thành phần" trên trang chủ của AngularJS , có ví dụ này:

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);
  }
}

Lưu ý cách selectphương thức được thêm vào $scope, nhưng addPanephương thức được thêm vào this. Nếu tôi thay đổi nó $scope.addPane, mã bị phá vỡ.

Tài liệu nói rằng trên thực tế có một sự khác biệt, nhưng nó không đề cập đến sự khác biệt đó là gì:

Các phiên bản trước của Angular (trước 1.0 RC) cho phép bạn sử dụng thisthay thế cho nhau với $scopephương thức, nhưng điều này không còn đúng nữa. Bên trong các phương thức được xác định trên phạm vi this$scopecó thể hoán đổi cho nhau (tập hợp góc thisthành $scope), nhưng không phải bên trong hàm tạo bộ điều khiển của bạn.

Làm thế nào this$scopelàm việc trong bộ điều khiển AngularJS?


Tôi thấy điều này cũng khó hiểu. Khi một khung nhìn chỉ định một bộ điều khiển (ví dụ: ng-controller = '...'), phạm vi $ được liên kết với bộ điều khiển đó dường như đi kèm với nó, bởi vì khung nhìn có thể truy cập các thuộc tính $ scope. Nhưng khi một lệnh 'yêu cầu bộ điều khiển khác (và sau đó sử dụng nó trong chức năng liên kết của nó), phạm vi $ được liên kết với bộ điều khiển khác đó không đi cùng với nó?
Mark Rajcok

Có phải câu nói khó hiểu về "Phiên bản trước ..." đã bị xóa bây giờ không? Sau đó, có thể cập nhật sẽ được đưa ra?
Dmitri Zaitsev

Đối với thử nghiệm đơn vị, nếu bạn sử dụng 'this' thay vì '$ scope', bạn không thể tiêm bộ điều khiển với phạm vi bị chế giễu và do đó bạn không thể thực hiện thử nghiệm đơn vị. Tôi không nghĩ rằng đó là một cách thực hành tốt để sử dụng 'cái này'.
abentan

Câu trả lời:


999

"Làm thế nào this$scopehoạt động trong bộ điều khiển AngularJS?"

Câu trả lời ngắn gọn :

  • this
    • Khi hàm xây dựng bộ điều khiển được gọi, thislà bộ điều khiển.
    • Khi một hàm được định nghĩa trên một $scopeđối tượng được gọi, thislà "phạm vi có hiệu lực khi hàm được gọi". Điều này có thể (hoặc có thể không!) $scopeLà chức năng được xác định trên. Vì vậy, bên trong chức năng, this$scopecó thể không giống nhau.
  • $scope
    • Mỗi bộ điều khiển có một $scopeđối tượng liên quan .
    • Hàm điều khiển (hàm tạo) chịu trách nhiệm thiết lập các thuộc tính mô hình và các hàm / hành vi trên mô hình được liên kết $scope.
    • Chỉ các phương thức được xác định trên $scopeđối tượng này (và các đối tượng phạm vi cha, nếu kế thừa nguyên mẫu đang hoạt động) có thể truy cập được từ HTML / view. Ví dụ: từ ng-click, bộ lọc, v.v.

Câu trả lời dài :

Hàm điều khiển là hàm xây dựng JavaScript. Khi hàm xây dựng thực thi (ví dụ: khi chế độ xem tải), this(nghĩa là "bối cảnh chức năng") được đặt thành đối tượng điều khiển. Vì vậy, trong hàm tạo của trình điều khiển "tab", khi hàm addPane được tạo

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

nó được tạo trên đối tượng điều khiển, không phải trên phạm vi $. Các khung nhìn không thể thấy hàm addPane - chúng chỉ có quyền truy cập vào các hàm được xác định trên $ scope. Nói cách khác, trong HTML, điều này sẽ không hoạt động:

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

Sau khi hàm xây dựng bộ điều khiển "tab" thực thi, chúng ta có các bước sau:

sau chức năng xây dựng trình điều khiển tab

Đường màu đen nét đứt biểu thị sự kế thừa nguyên mẫu - một phạm vi cô lập kế thừa nguyên mẫu từ Phạm vi . (Nó không được kế thừa nguyên mẫu từ phạm vi có hiệu lực trong đó lệnh được gặp trong HTML.)

Bây giờ, chức năng liên kết của chỉ thị khung muốn giao tiếp với chỉ thị tab (điều này thực sự có nghĩa là nó cần ảnh hưởng đến các tab cách ly $ scope theo một cách nào đó). Các sự kiện có thể được sử dụng, nhưng một cơ chế khác là có khung chỉ thị requiređiều khiển tab. (Dường như không có cơ chế nào cho chỉ thị khung cho requirecác tab $ scope.)

Vì vậy, điều này đặt ra câu hỏi: nếu chúng ta chỉ có quyền truy cập vào trình điều khiển tab, làm thế nào để chúng ta có quyền truy cập vào các tab cô lập $ scope (đó là điều chúng ta thực sự muốn)?

Vâng, đường chấm màu đỏ là câu trả lời. "Phạm vi" của hàm addPane () (Tôi đang đề cập đến phạm vi / đóng của hàm JavaScript ở đây) cung cấp cho hàm truy cập vào các tab cách ly $ scope. Tức là addPane () có quyền truy cập vào "tab IsolateScope" trong sơ đồ ở trên vì một bao đóng được tạo khi addPane () được xác định. (Nếu chúng ta thay vào đó đã xác định addPane () trên đối tượng tab scope scope, lệnh directive sẽ không có quyền truy cập vào hàm này và do đó nó sẽ không có cách nào để giao tiếp với các tab $ scope.)

Để trả lời phần khác của câu hỏi của bạn how does $scope work in controllers?::

Trong các hàm được định nghĩa trên $ scope, thisđược đặt thành "phạm vi $ có hiệu lực trong đó / khi hàm được gọi". Giả sử chúng ta có HTML sau:

<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(Hoàn toàn) có

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

Nhấp vào liên kết đầu tiên sẽ hiển thị this$scopegiống nhau, vì " phạm vi có hiệu lực khi hàm được gọi " là phạm vi được liên kết với ParentCtrl.

Nhấn vào liên kết thứ hai sẽ tiết lộ this$scopekhông giống nhau, vì " phạm vi hiệu lực khi hàm được gọi " là phạm vi liên quan đến việc ChildCtrl. Vì vậy, ở đây, thisđược thiết lập để ChildCtrl's $scope. Bên trong phương thức, $scopevẫn ParentCtrllà phạm vi $ s.

Vĩ cầm

Tôi cố gắng không sử dụng thisbên trong hàm được xác định trên $ scope, vì nó trở nên khó hiểu khi phạm vi $ đang bị ảnh hưởng, đặc biệt là xem xét rằng ng-repeat, ng-include, ng-switch và chỉ thị đều có thể tạo phạm vi con của riêng chúng.


6
@tamakisapes, tôi tin rằng văn bản in đậm mà bạn trích dẫn áp dụng cho khi hàm xây dựng bộ điều khiển được gọi - tức là khi bộ điều khiển được tạo = liên kết với phạm vi $. Nó không áp dụng sau này, khi mã JavaScript tùy ý gọi một phương thức được xác định trên một đối tượng $ scope.
Mark Rajcok

79
Lưu ý rằng bây giờ có thể gọi hàm addPane () trực tiếp trong mẫu bằng cách đặt tên bộ điều khiển: "MyContoder là myctrl" và sau đó là myctrl.addPane (). Xem docs.angularjs.org/guide/con accept # điều khiển
Augier

81
Quá nhiều phức tạp vốn có.
Inanc Gumus

11
Đây là một câu trả lời rất nhiều thông tin, nhưng khi tôi trở lại với một vấn đề thực tế ( làm thế nào để gọi $ scope. $ Áp dụng () trong một phương thức điều khiển được xác định bằng cách sử dụng 'this' ), tôi không thể giải quyết được. Vì vậy, trong khi đây vẫn là một câu trả lời hữu ích, tôi đang tìm thấy sự khó hiểu "phức tạp vốn có".
dumbledad

11
Javascript - rất nhiều dây [để treo mình].
AlikElzin-kilaka

55

Lý do 'addPane' được gán cho điều này là vì <pane>chỉ thị.

Lệnh panenày require: '^tabs'sẽ đưa đối tượng điều khiển tab từ lệnh cha vào hàm liên kết.

addPaneđược gán cho thisđể panechức năng liên kết có thể nhìn thấy nó. Sau đó, trong panechức năng liên kết, addPanechỉ là một thuộc tính của tabsbộ điều khiển và nó chỉ là các tabControllObject.addPane. Vì vậy, chức năng liên kết của chỉ thị khung có thể truy cập vào đối tượng điều khiển tab và do đó truy cập phương thức addPane.

Tôi hy vọng lời giải thích của tôi đủ rõ ràng .. thật khó để giải thích.


3
Cảm ơn đã giải thích. Các tài liệu làm cho có vẻ như bộ điều khiển chỉ là một chức năng thiết lập phạm vi. Tại sao bộ điều khiển được xử lý như một đối tượng nếu tất cả các hành động xảy ra trong phạm vi? Tại sao không chỉ vượt qua phạm vi cha mẹ vào chức năng liên kết? Chỉnh sửa: Để diễn đạt tốt hơn câu hỏi này, nếu cả hai phương thức điều khiển và phương thức phạm vi hoạt động trên cùng một cấu trúc dữ liệu (phạm vi), tại sao không đặt tất cả chúng ở một nơi?
Alexei Boronine

Có vẻ như phạm vi cha mẹ không được truyền vào lnk func vì mong muốn hỗ trợ "các thành phần có thể tái sử dụng, không nên vô tình đọc hoặc sửa đổi dữ liệu trong phạm vi cha mẹ". Nhưng nếu một lệnh thực sự muốn / cần đọc hoặc sửa đổi MỘT SỐ dữ liệu CỤ THỂ trong phạm vi cha (như chỉ thị 'khung'), thì nó đòi hỏi một số nỗ lực: 'yêu cầu' bộ điều khiển có phạm vi cha mong muốn, sau đó xác định phương thức trên bộ điều khiển đó (sử dụng 'this' not $ scope) để truy cập dữ liệu cụ thể. Vì phạm vi cha mẹ mong muốn không được đưa vào func lnk, tôi cho rằng đây là cách duy nhất để làm điều đó.
Mark Rajcok

1
Này, thực sự dễ dàng hơn để sửa đổi phạm vi của chỉ thị. Bạn chỉ có thể sử dụng chức năng liên kết jsfiddle.net/TuNyj
Andrew Joslin

3
Cảm ơn @Andy cho fiddle. Trong fiddle của bạn, lệnh này không tạo ra một phạm vi mới, vì vậy tôi có thể thấy cách chức năng liên kết có thể truy cập trực tiếp vào phạm vi của bộ điều khiển ở đây (vì chỉ có một phạm vi). Các tab và chỉ thị khung sử dụng phạm vi cô lập (nghĩa là phạm vi con mới được tạo không kế thừa nguyên mẫu từ phạm vi cha). Đối với trường hợp phạm vi cô lập, dường như việc xác định một phương thức trên bộ điều khiển (sử dụng 'this') là cách duy nhất để cho phép một lệnh khác có được quyền truy cập (gián tiếp) vào phạm vi (bị cô lập) khác.
Mark Rajcok

27

Tôi vừa đọc một lời giải thích khá thú vị về sự khác biệt giữa hai điều này, và một sở thích ngày càng tăng để gắn các mô hình vào bộ điều khiển và bí danh bộ điều khiển để liên kết các mô hình với khung nhìn. http://toddmotto.com/digging-into-angenses-controll-as-syntax/ là bài viết.
Anh ta không đề cập đến nó nhưng khi xác định chỉ thị, nếu bạn cần chia sẻ điều gì đó giữa nhiều chỉ thị và không muốn có dịch vụ (có những trường hợp hợp pháp khi dịch vụ gặp rắc rối) thì hãy đính kèm dữ liệu vào bộ điều khiển của lệnh cha.

Các $scopedịch vụ cung cấp rất nhiều điều hữu ích, $watchlà rõ ràng nhất, nhưng nếu tất cả các bạn cần phải dữ liệu ràng buộc để xem, sử dụng bộ điều khiển đơn giản và 'điều khiển như' trong mẫu là tốt và có lẽ thích hợp hơn.


20

Tôi khuyên bạn nên đọc bài viết sau: AngularJS: "Trình điều khiển là" hoặc "$ scope"?

Nó mô tả rất rõ những lợi thế của việc sử dụng "Trình điều khiển là" để hiển thị các biến trên "$ scope".

Tôi biết bạn đã hỏi cụ thể về các phương thức và không phải các biến, nhưng tôi nghĩ rằng tốt hơn là nên tuân thủ một kỹ thuật và nhất quán với nó.

Vì vậy, theo ý kiến ​​của tôi, do vấn đề về các biến được thảo luận trong bài viết, tốt hơn hết là chỉ sử dụng kỹ thuật "Trình điều khiển như" và cũng áp dụng nó cho các phương thức.


16

Trong khóa học này ( https://www.codeschool.com/cifts/shaping-up-with-angular-js ) họ giải thích cách sử dụng "cái này" và nhiều thứ khác.

Nếu bạn thêm phương thức vào bộ điều khiển thông qua phương thức "này", bạn phải gọi nó trong dạng xem với tên "chấm" thuộc tính của bộ điều khiển.

Ví dụ: sử dụng bộ điều khiển của bạn trong chế độ xem, bạn có thể có mã như thế này:

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

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

    </div>

6
Sau khi trải qua khóa học, tôi đã ngay lập tức bối rối bởi việc sử dụng mã $scope, vì vậy cảm ơn vì đã đề cập đến nó.
Matt Montag

16
Khóa học đó hoàn toàn không đề cập đến phạm vi $, họ chỉ sử dụng asthisvậy nó có thể giúp giải thích sự khác biệt như thế nào?
dumbledad

10
Lần chạm đầu tiên của tôi với Angular là từ khóa học đã đề cập, và như $scopechưa bao giờ được nhắc đến, tôi đã học cách sử dụng chỉ thistrong bộ điều khiển. Vấn đề là khi bạn bắt đầu thực hiện các lời hứa trong bộ điều khiển của mình, bạn có rất nhiều vấn đề tham chiếu thisvà phải bắt đầu thực hiện những việc như var me = thistham chiếu mô hình thistừ bên trong hàm trả về lời hứa. Vì vậy, tôi vẫn rất bối rối không biết nên sử dụng phương pháp nào, $scopehay this.
Bruno Finger

@BrunoFinger Thật không may, bạn sẽ cần var me = thishoặc .bind(this)bất cứ khi nào bạn thực hiện Lời hứa hoặc những thứ nặng nề khác. Nó không có gì để làm với Angular.
Dzmitry Lazerka 18/03/2016

1
Điều quan trọng là phải biết rằng ng-controller="MyCtrl as MC"nó tương đương với việc đặt $scope.MC = thischính bộ điều khiển - nó xác định một thể hiện (cái này) của MyCtrl trên phạm vi để sử dụng trong mẫu qua{{ MC.foo }}
William B

3

Các phiên bản trước của Angular (trước 1.0 RC) cho phép bạn sử dụng thay thế cho nhau với phương thức $ scope, nhưng điều này không còn đúng nữa. Bên trong các phương thức được xác định trên phạm vi này và phạm vi $ có thể hoán đổi cho nhau (góc đặt giá trị này thành $ scope), nhưng không phải bên trong hàm tạo bộ điều khiển của bạn.

Để mang lại hành vi này (có ai biết tại sao nó lại thay đổi không?) Bạn có thể thêm:

return angular.extend($scope, this);

ở cuối chức năng điều khiển của bạn (với điều kiện $ scope đã được thêm vào chức năng điều khiển này).

Điều này có một hiệu ứng tốt khi có quyền truy cập vào phạm vi cha mẹ thông qua đối tượng điều khiển mà bạn có thể có trong con require: '^myParentDirective'


7
Bài viết này cung cấp một lời giải thích tốt về lý do tại sao điều này và phạm vi $ là khác nhau.
Robert Martin

1

$ scope có một 'this' khác với bộ điều khiển 'this'.Vì vậy, nếu bạn đặt console.log (this) bên trong bộ điều khiển, nó cung cấp cho bạn một đối tượng (bộ điều khiển) và this.addPane () thêm Phương thức addPane vào Đối tượng điều khiển. Nhưng phạm vi $ có phạm vi khác nhau và tất cả phương thức trong phạm vi của nó cần được tích hợp bởi $ scope.methodName (). this.methodName()bên trong bộ điều khiển có nghĩa là thêm methos bên trong đối tượng điều khiển. $scope.functionName()có trong HTML và bên trong

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

Dán mã này vào trình chỉnh sửa của bạn và mở bảng điều khiển để xem ...

 <!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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.