AngularJS - $ phá hủy loại bỏ người nghe sự kiện?


200

https://docs.angularjs.org/guide/directive

Bằng cách lắng nghe sự kiện này, bạn có thể loại bỏ các trình lắng nghe sự kiện có thể gây rò rỉ bộ nhớ. Trình nghe được đăng ký phạm vi và phần tử sẽ tự động được dọn sạch khi chúng bị hủy, nhưng nếu bạn đã đăng ký trình nghe trên dịch vụ hoặc đăng ký trình nghe trên nút DOM không bị xóa, bạn sẽ phải tự dọn sạch hoặc bạn có nguy cơ giới thiệu rò rỉ bộ nhớ.

Thực hành tốt nhất: Chỉ thị nên tự dọn dẹp. Bạn có thể sử dụng phần tử.on ('$ kill', ...) hoặc scope. $ On ('$ kill', ...) để chạy chức năng dọn dẹp khi lệnh bị xóa.

Câu hỏi:

Tôi có một element.on "click", (event) ->chỉ thị bên trong:

  1. Khi lệnh bị hủy, có bất kỳ tham chiếu bộ nhớ nào element.onđể giữ cho nó không bị thu thập rác không?
  2. Tài liệu góc nói rằng tôi nên sử dụng một trình xử lý để loại bỏ các trình lắng nghe sự kiện về $destroysự kiện được phát ra. Tôi đã có ấn tượng rằng destroy()loại bỏ người nghe sự kiện, đây không phải là trường hợp?

Câu trả lời:


433

Người nghe sự kiện

Trước hết, điều quan trọng là phải hiểu rằng có hai loại "người nghe sự kiện":

  1. Phạm vi người nghe sự kiện đã đăng ký thông qua $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
  2. Trình xử lý sự kiện được đính kèm với các phần tử thông qua ví dụ onhoặc bind:

    element.on('click', function (event) {
      ...
    });

$ phạm vi. $ phá hủy ()

Khi $scope.$destroy()được thực thi, nó sẽ xóa tất cả các trình nghe được đăng ký qua $ontrên phạm vi $ đó.

Nó sẽ không loại bỏ các thành phần DOM hoặc bất kỳ trình xử lý sự kiện đính kèm nào thuộc loại thứ hai.

Điều này có nghĩa là việc gọi $scope.$destroy()thủ công từ ví dụ trong hàm liên kết của lệnh sẽ không xóa trình xử lý được đính kèm qua element.on, cũng như chính phần tử DOM.


phần tử.remove ()

Lưu ý rằng đó removelà phương thức jqLite (hoặc phương thức jQuery nếu jQuery được tải trước AngularjS) và không khả dụng trên Đối tượng phần tử DOM tiêu chuẩn.

Khi element.remove()được thực thi phần tử đó và tất cả các phần tử con của nó sẽ bị xóa khỏi DOM cùng với tất cả các trình xử lý sự kiện được đính kèm qua chẳng hạn element.on.

Nó sẽ không phá hủy phạm vi $ liên quan đến phần tử.

Để làm cho nó khó hiểu hơn, cũng có một sự kiện jQuery được gọi là $destroy. Đôi khi, khi làm việc với các thư viện jQuery của bên thứ ba loại bỏ các phần tử hoặc nếu bạn xóa chúng theo cách thủ công, bạn có thể cần phải thực hiện dọn dẹp khi điều đó xảy ra:

element.on('$destroy', function () {
  scope.$destroy();
});

Phải làm gì khi chỉ thị bị "phá hủy"

Điều này phụ thuộc vào cách chỉ thị bị "phá hủy".

Một trường hợp bình thường là một lệnh bị hủy vì ng-viewthay đổi chế độ xem hiện tại. Khi điều này xảy ra, lệnh ng-viewsẽ hủy phạm vi $ được liên kết, cắt tất cả các tham chiếu đến phạm vi chính của nó và gọi remove()phần tử.

Điều này có nghĩa là nếu chế độ xem đó chứa một lệnh với chức năng này trong chức năng liên kết của nó khi nó bị phá hủy bởi ng-view:

scope.$on('anEvent', function () {
 ...
});

element.on('click', function () {
 ...
});

Cả hai người nghe sự kiện sẽ được gỡ bỏ tự động.

Tuy nhiên, điều quan trọng cần lưu ý là mã bên trong các trình nghe này vẫn có thể gây rò rỉ bộ nhớ, ví dụ nếu bạn đã đạt được mẫu rò rỉ bộ nhớ JS phổ biến circular references.

Ngay cả trong trường hợp bình thường này của một lệnh bị phá hủy do thay đổi chế độ xem, có những thứ bạn có thể cần phải làm sạch bằng tay.

Ví dụ: nếu bạn đã đăng ký một người nghe trên $rootScope:

var unregisterFn = $rootScope.$on('anEvent', function () {});

scope.$on('$destroy', unregisterFn);

Điều này là cần thiết vì $rootScopekhông bao giờ bị phá hủy trong suốt vòng đời của ứng dụng.

Điều tương tự cũng xảy ra nếu bạn đang sử dụng một triển khai pub / sub khác không tự động thực hiện việc dọn dẹp cần thiết khi phạm vi $ bị hủy hoặc nếu lệnh của bạn chuyển các cuộc gọi lại đến các dịch vụ.

Một tình huống khác sẽ là hủy bỏ $interval/ $timeout:

var promise = $interval(function () {}, 1000);

scope.$on('$destroy', function () {
  $interval.cancel(promise);
});

Nếu chỉ thị của bạn đính kèm các trình xử lý sự kiện vào các thành phần, ví dụ bên ngoài chế độ xem hiện tại, bạn cũng cần phải xóa sạch chúng theo cách thủ công:

var windowClick = function () {
   ...
};

angular.element(window).on('click', windowClick);

scope.$on('$destroy', function () {
  angular.element(window).off('click', windowClick);
});

Đây là một số ví dụ về những việc cần làm khi các chỉ thị bị "phá hủy" bởi Angular, ví dụ bởi ng-viewhoặc ng-if.

Nếu bạn có các chỉ thị tùy chỉnh quản lý vòng đời của các phần tử DOM, v.v ... tất nhiên nó sẽ phức tạp hơn.


4
'$ rootScope không bao giờ bị phá hủy trong suốt vòng đời của ứng dụng.' : rõ ràng một khi bạn nghĩ về nó. Đó là những gì tôi đã thiếu.
dùng276648

@tasseKATT Một câu hỏi nhỏ ở đây, Nếu trong cùng một bộ điều khiển, chúng ta có nhiều $ rootScope. $ cho các sự kiện khác nhau, thì chúng ta sẽ gọi $ scope. $ on ("$ kill", ListenerName1); cho mỗi $ rootScope. $ trên khác nhau ??
Yashika Garg

2
@YashikaGarg Có lẽ dễ nhất là chỉ có chức năng trợ giúp gọi tất cả người nghe. Giống như $ scope. $ On ('$ kill'), function () {ListenerName1 (); ListenerName2 (); ...}); Có bất kỳ sự phức tạp bổ sung nào cho $ trên các trình xử lý sự kiện trên phạm vi không cô lập không? Hoặc cô lập phạm vi với ràng buộc hai chiều?
David Rice

Tại sao đăng ký người nghe sự kiện trên $ rootscope? Tôi đăng ký trình lắng nghe sự kiện trên $ scope và sau đó các trình điều khiển khác thực hiện $ rootscope.broadcast ('tên sự kiện') và trình nghe sự kiện của tôi chạy. Những người nghe sự kiện trên phạm vi $ đang nghe các sự kiện ứng dụng vẫn sẽ được tự động làm sạch?
Skychan

@Skychan Xin lỗi tôi đã bỏ lỡ bình luận của bạn. Đây là một phỏng đoán, nhưng mọi người có thể sử dụng $rootScopevì điều này: stackoverflow.com/questions/11252780/ Khăn Lưu ý rằng như câu trả lời ở trên cùng, điều này đã được thay đổi. Có, người nghe sự kiện trên bình thường $scopesẽ được tự động làm sạch khi phạm vi đó bị phá hủy.
tasseKATT
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.