1. 程式人生 > >angular中釋出/訂閱模式詳解

angular中釋出/訂閱模式詳解

Angular按照釋出/訂閱模式設計了其事件系統,使用時需要“釋出”事件,並在適當的位置“訂閱”或“退訂”事件,就像郵箱裡面大量的訂閱郵件一樣,當我們不需要時就可以將其退訂了。具體到開發中,對應著 </span>scope<span> rootScope的$emit</code>、<code>$broadcast$on</code>方法。本文介紹Angular的事件機制,包括<span>$scope和$</span>rootScope處理事件上的異同,<span>$

broadcast、$</span>emit和<span>$on的使用方式及他們區別等內容。

</span>scope<span> scope之間的關係, </span>scope rootScope之間的關係

要理解Angular的事件機制,首先需要了解$scope</code>與<code>$scope之間的關係以及$scope</code>與<code>$rootScope之間的關係。$rootScope</code>是唯一真神,是萬域起源,是所有<code>$scope

的最終祖先。而$scope</code>與<code>$scope之間可能的關係包括父子關係和兄弟關係。還記得controller之間的關係嗎,Angular為每個controller分配一個獨立的$scope</code>,controller之間的關係也對應著<code>$scope之間的關係:

<div ng-controller="ParentCtrl as parent">
    {{ parent.data }}
    <div ng-controller="SiblingOneCtrl as sib1">
{{ sib1.data }} </div> <div ng-controller="SiblingTwoCtrl as sib2"> {{ sib2.data }} </div> </div>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    釋出、訂閱、退訂

    $broadcast</code>和<code>$emit用於釋出事件,他們將事件名稱和事件內容釋出出去,就像是高考榜單一樣,事件名稱相當於考生的名字,而事件內容相當於考生的成績等資訊:

    $scope.$broadcast('EVENT_NAME', 'Data to send');
    $scope.$emit('EVENT_NAME', 'Data to send');
      
    • 1
    • 2
    • 1
    • 2

    $on用於訂閱事件,事件名稱是訂閱的唯一標識,每個考生看榜單時都要尋找自己的名字,然後根據自己的成績等資訊決定下一步應該報考什麼學校:

    $scope.$on('EVENT_NAME', function(event, args) {
        // balabala
    });
      
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    Angular的退訂事件有些奇怪,並沒有類似於其他語言的$off方法,所以不要想當然的按照如下方式進行事件的退訂操作:

    // 不要這樣做
    $scope.$off('EVENT_NAME');
      
    • 1
    • 2
    • 1
    • 2

    事實上,Angular的事件退訂方法隱藏在事件訂閱裡面:使用$on訂閱事件時會返回一個函式,而此函式就是用來退訂事件的方法,就像是考生看到了自己的成績後稟告父母大人,“商量著”選取學校填報志願,而此志願單就是結束整個高考榜單的結束:

    // 訂閱事件返回用於退訂事件的函式
    var deregister = $scope.$on('EVENT_NAME', function(event, args) {
        // balabala
    });
    
    // 退訂事件
    deregister();
      

        </span>broadcast</strong><strong> emit相當於射箭

        $broadcast</code>和<code>$emit都用於釋出事件,但從名字就可以看出他們的不同點:$broadcast</code>是自上而下的廣播,所有能聽到的都可以對其進行反應。而<code>$emit是自下而上的射箭,只有在箭矢的軌跡上才能對其做出反應。

        具體到Angular上,即從一個$scope</code>上通過<code>$broadcast釋出的事件,他的所有後代$scope都可以對此事件做出響應:

        // 父$scope通過$broadcast釋出事件
        app.controller('ParentCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
            $scope.$broadcast("parent", 'Data to Send');
        }])
        //所有子$scope都可以通過$on訂閱事件
        .controller('SiblingOneCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
            $scope.$on("parent", function(event, 'Data to Send') {
                // balabala
            });
        }])
        .controller('SiblingTwoCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
            $scope.$on("parent", function(event, 'Data to Send') {
                // balabala
            });
        }]);
          

            而通過$emit</code>釋出的事件,只有他的祖先<code>$scope可以做出響應,並且其中任一祖先都可以將此事件終結掉,不讓其繼續傳播:

            // 子$scope通過$emit釋出事件
            app.controller('SiblingOneCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                $scope.$emit("sib1", 'Data to Send');
            }])
            // 父$scope通過$on訂閱事件
            .controller('ParentCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                $scope.$on("sib1", function(event, 'Data to Send') {
                    // balabala
                });
            }])
            // 其兄弟$scope對其$emit的事件一無所知,所以不能訂閱其事件
            .controller('SiblingTwoCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                // 不要這樣做
                $scope.$on("sib1", function(event, 'Data to Send') {
                    // balabala
                });
            }]);
              

                $emit釋出事件的響應道路上,其任一祖先如果感覺不再需要此事件了,就可以通過如下方式終結此事件:

                app.controller('ParentCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                    $scope.$on("sib1", function(event, 'Data to Send') {
                        // balabala
                        event.stopPropagation(); // 終止事件繼續“冒泡”
                    });
                }])
                  

                    </span>rootScope<span> broadcast和$emit

                    上面說過$rootScope</code>是所有<code>$scope的最終祖先,所以通過$rootScope</code>的<code>$broadcast釋出的事件可以被所有$scope</code>接收到,包括<code>$rootScope

                    app.controller('SomeCtrl', ['$rootScope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($rootScope) {
                        $rootScope.$broadcast("rootEvent", 'Data to Send');
                    
                        // $rootScope也可以通過$on訂閱從$rootScope.$broadcast釋出的事件
                        $rootScope.$on("rootEvent", function(event, 'Data to Send') {
                            // balabala
                        });
                    }])
                    // 所有$scope都能夠通過$on訂閱從$rootScope.$broadcast釋出的事件
                    .controller('ParentCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                        $scope.$on("rootEvent", function(event, 'Data to Send') {
                            // balabala
                        });
                    }])
                    .controller('SiblingOneCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                        $scope.$on("rootEvent", function(event, 'Data to Send') {
                            // balabala
                        });
                    }])
                      

                        $rootScope</code>的<code>$emit就有些怪異了,按照上面的描述,$rootScope</code>是沒有祖先的,所以我們可能會想到其<code>$emit會沒有任何作用,但事實並不如此$rootScope.$emit釋出的事件,只能通過$rootScope.$on訂閱,而其他$scope對此一無所知:

                        app.controller('SomeCtrl', ['$rootScope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($rootScope) {
                            $rootScope.$emit("rootEvent", 'Data to Send');
                        
                            // 只有$rootScope可以通過$on訂閱從$rootScope.$emit釋出的事件
                            $rootScope.$on("rootEvent", function(event, 'Data to Send') {
                                // balabala
                            });
                        }])
                        // $scope不能夠通過$on訂閱從$rootScope.$emit釋出的事件
                        .controller('ParentCtrl', ['$scope'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope) {
                            // 不要這樣做
                            $scope.$on("rootEvent", function(event, 'Data to Send') {
                                // balabala
                            });
                        }]);
                          

                          退訂$rootScope上的訂閱事件

                          當使用$rootScope.$on訂閱事件時,需要手動退訂事件,一般在其所處$scope</code>的<code>$destory事件中退訂:

                          app.controller('SomeCtrl', ['$rootScope'</span>, <span class="hljs-string">'$scope', function($rootScope, $scope) {
                              var deregister = $rootScope.$on("rootEvent", function(event, 'Data to Send') {
                                  // balabala
                              });
                          
                              $scope.$on('$destory', function() {
                                  deregister(); // 退訂事件
                              });
                          }])
                            

                              那通過$scope.$on訂閱的事件呢?一般不需要手動退訂,因為Angular會幫我們退訂,但是如果需要自己控制何時退訂事件,也可以通過上述方式進行退訂。

                              事件命名的建議

                              在開發中,對於變數的命名、函式的命名、檔案的命名都有一定的規範,既要保證可讀性,也需要保證無混淆性。在Angular的事件機制中,因為事件可能會跨函式,甚至可能跨檔案,所以對於事件名一定要保證唯一性,所以建議事件名都加上特定的字首,以便區分。如下幾個例子:

                              $scope.$emit('trash:delete', data);
                              $scope.$on('trash:delete', function (event, data) {...});
                              
                              $scope.$broadcast('trash:clear', data);
                              $scope.$on('trash:clear', function (event, data) {...});
                                
                              • 1
                              • 2
                              • 3
                              • 4
                              • 5

                                結語

                                Angular的事件機制很智慧,而且一般來說總能符合我們的預期,但是如果不能深入理解其背後的機制,可能會踏入某些深坑,本文嘗試說明Angular的事件機制,如果有理解不正確的地方,請留言告知。