浅谈Angular 中何时取消订阅


Posted in Javascript onNovember 22, 2017

你可能知道当你订阅 Observable 对象或设置事件监听时,在某个时间点,你需要执行取消订阅操作,进而释放操作系统的内存。否则,你的应用程序可能会出现内存泄露。

接下来让我们看一下,需要在 ngOnDestroy 生命周期钩子中,手动执行取消订阅操作的一些常见场景。

手动释放资源场景

表单

export class TestComponent {

 ngOnInit() {
  this.form = new FormGroup({...});
  // 监听表单值的变化
  this.valueChanges = this.form.valueChanges.subscribe(console.log);
  // 监听表单状态的变化              
  this.statusChanges = this.form.statusChanges.subscribe(console.log);
 }

 ngOnDestroy() {
  this.valueChanges.unsubscribe();
  this.statusChanges.unsubscribe();
 }
}

以上方案也适用于其它的表单控件。

路由

export class TestComponent {
 constructor(private route: ActivatedRoute, private router: Router) { }

 ngOnInit() {
  this.route.params.subscribe(console.log);
  this.route.queryParams.subscribe(console.log);
  this.route.fragment.subscribe(console.log);
  this.route.data.subscribe(console.log);
  this.route.url.subscribe(console.log);
  
  this.router.events.subscribe(console.log);
 }

 ngOnDestroy() {
  // 手动执行取消订阅的操作
 }
}

Renderer 服务

export class TestComponent {
 constructor(
  private renderer: Renderer2, 
  private element : ElementRef) { }

 ngOnInit() {
  this.click = this.renderer
    .listen(this.element.nativeElement, "click", handler);
 }

 ngOnDestroy() {
  this.click.unsubscribe();
 }
}

Infinite Observables

当你使用 interval() 或 fromEvent() 操作符时,你创建的是一个无限的 Observable 对象。这样的话,当我们不再需要使用它们的时候,就需要取消订阅,手动释放资源。

export class TestComponent {
 constructor(private element : ElementRef) { }

 interval: Subscription;
 click: Subscription;

 ngOnInit() {
  this.interval = Observable.interval(1000).subscribe(console.log);
  this.click = Observable.fromEvent(this.element.nativeElement, 'click')
              .subscribe(console.log);
 }

 ngOnDestroy() {
  this.interval.unsubscribe();
  this.click.unsubscribe();
 }
}

Redux Store

export class TestComponent {

 constructor(private store: Store) { }

 todos: Subscription;

 ngOnInit() {
   /**
   * select(key : string) {
   *  return this.map(state => state[key]).distinctUntilChanged();
   * }
   */
   this.todos = this.store.select('todos').subscribe(console.log); 
 }

 ngOnDestroy() {
  this.todos.unsubscribe();
 }
}

无需手动释放资源场景

AsyncPipe

@Component({
 selector: 'test',
 template: `<todos [todos]="todos$ | async"></todos>`
})
export class TestComponent {
 constructor(private store: Store) { }
 
 ngOnInit() {
   this.todos$ = this.store.select('todos');
 }
}

当组件销毁时,async 管道会自动执行取消订阅操作,进而避免内存泄露的风险。

Angular AsyncPipe 源码片段

@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {
 // ...
 constructor(private _ref: ChangeDetectorRef) {}

 ngOnDestroy(): void {
  if (this._subscription) {
   this._dispose();
  }
 }
}

@HostListener

export class TestDirective {
 @HostListener('click')
 onClick() {
  ....
 }
}

需要注意的是,如果使用 @HostListener 装饰器,添加事件监听时,我们无法手动取消订阅。如果需要手动移除事件监听的话,可以使用以下的方式:

// subscribe
this.handler = this.renderer.listen('document', "click", event =>{...});

// unsubscribe
this.handler();

Finite Observable

当你使用 HTTP 服务或 timer Observable 对象时,你也不需要手动执行取消订阅操作。

export class TestComponent {
 constructor(private http: Http) { }

 ngOnInit() {
  // 表示1s后发出值,然后就结束了
  Observable.timer(1000).subscribe(console.log);
  this.http.get('http://api.com').subscribe(console.log);
 }
}

timer 操作符

操作符签名

public static timer(initialDelay: number | Date, period: number, scheduler: Scheduler): Observable

操作符作用

timer 返回一个发出无限自增数列的 Observable,具有一定的时间间隔,这个间隔由你来选择。

操作符示例

// 每隔1秒发出自增的数字,3秒后开始发送
var numbers = Rx.Observable.timer(3000, 1000);
numbers.subscribe(x => console.log(x));

// 5秒后发出一个数字
var numbers = Rx.Observable.timer(5000);
numbers.subscribe(x => console.log(x));

最终建议

你应该尽可能少的调用 unsubscribe() 方法,你可以在RxJS: Don't Unsubscribe 这篇文章中了解与 Subject 相关更多信息。

具体示例如下:

export class TestComponent {
 constructor(private store: Store) { }

 private componetDestroyed: Subject = new Subject();
 todos: Subscription;
 posts: Subscription;

 ngOnInit() {
   this.todos = this.store.select('todos')
           .takeUntil(this.componetDestroyed).subscribe(console.log); 
           
   this.posts = this.store.select('posts')
           .takeUntil(this.componetDestroyed).subscribe(console.log); 
 }

 ngOnDestroy() {
  this.componetDestroyed.next();
  this.componetDestroyed.unsubscribe();
 }
}

takeUntil 操作符

操作符签名

public takeUntil(notifier: Observable): Observable<T>

操作符作用

发出源 Observable 发出的值,直到 notifier Observable 发出值。

操作符示例

var interval = Rx.Observable.interval(1000);
var clicks = Rx.Observable.fromEvent(document, 'click');
var result = interval.takeUntil(clicks);

result.subscribe(x => console.log(x));

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript中用星号表示预录入内容的实现代码
Jan 08 Javascript
深入理解JavaScript系列(8) S.O.L.I.D五大原则之里氏替换原则LSP
Jan 15 Javascript
JQuery创建DOM节点的方法
Jun 11 Javascript
JS+CSS实现仿雅虎另类滑动门切换效果
Oct 13 Javascript
基于JavaScript创建动态Dom
Dec 08 Javascript
基于js对象,操作属性、方法详解
Aug 11 Javascript
jQuery的 $.ajax防止重复提交的两种方法(推荐)
Oct 14 Javascript
原生JS实现的放大镜效果实例代码
Oct 15 Javascript
详解jQuery中ajax.load()方法
Jan 25 Javascript
xmlplus组件设计系列之选项卡(Tabbar)(5)
May 03 Javascript
Vue脚手架的简单使用实例
Jul 10 Javascript
vue element 中的table动态渲染实现(动态表头)
Nov 21 Javascript
深入理解Angular4订阅(Subscribe)与取消
Nov 22 #Javascript
利用vue + koa2 + mockjs模拟数据的方法教程
Nov 22 #Javascript
详解从零搭建 vue2 vue-router2 webpack3 工程
Nov 22 #Javascript
利用vue+elementUI实现部分引入组件的方法详解
Nov 22 #Javascript
通过一个简单的例子学会vuex与模块化
Nov 22 #Javascript
第一个Vue插件从封装到发布
Nov 22 #Javascript
详细分析单线程JS执行问题
Nov 22 #Javascript
You might like
PHP 上传文件大小限制
2009/07/05 PHP
php blowfish加密解密算法
2016/07/02 PHP
Laravel 集成微信用户登录和绑定的实现
2019/12/27 PHP
jQuery下的几个你可能没用过的功能
2010/08/29 Javascript
jQuery 源码分析笔记(4) Ready函数
2011/06/02 Javascript
引入JS文件IE6报语法错误或缺少对象问题的解决方法
2014/01/09 Javascript
javascript学习笔记--数字格式类型
2014/05/22 Javascript
jQuery实现拖动调整表格单元格大小的代码实例
2015/01/13 Javascript
Javascript中Array用法实例分析
2015/06/13 Javascript
Bootstrap网格系统详解
2016/04/26 Javascript
AngularJS ng-bind-html 指令详解及实例代码
2016/07/30 Javascript
JS使用onerror捕获异常示例
2016/08/03 Javascript
nodejs集成sqlite使用示例
2017/06/05 NodeJs
jQuery取得元素标签名称小结(附代码)
2017/08/16 jQuery
详解react-native WebView 返回处理(非回调方法可解决)
2018/02/27 Javascript
vue+webpack 打包文件 404 页面空白的解决方法
2018/02/28 Javascript
详解VS Code使用之Vue工程配置format代码格式化
2019/03/20 Javascript
vue 解决文本框被键盘遮住的问题
2019/11/06 Javascript
vue组件中实现嵌套子组件案例
2020/08/31 Javascript
详解Vue中的watch和computed
2020/11/09 Javascript
重命名批处理python脚本
2013/04/05 Python
Python 爬虫多线程详解及实例代码
2016/10/08 Python
vue.js实现输入框输入值内容实时响应变化示例
2018/07/07 Python
在Pytorch中使用样本权重(sample_weight)的正确方法
2019/08/17 Python
使用python实现数组、链表、队列、栈的方法
2019/12/20 Python
python两个_多个字典合并相加的实例代码
2019/12/26 Python
pytorch 实现在预训练模型的 input上增减通道
2020/01/06 Python
python属于解释型语言么
2020/06/15 Python
pytorch 限制GPU使用效率详解(计算效率)
2020/06/27 Python
html5 利用canvas手写签名并保存的实现方法
2018/07/12 HTML / CSS
美国电视购物HSN官网:HSN
2016/09/07 全球购物
投标保密承诺书
2014/05/19 职场文书
党的群众路线教育实践活动整改落实情况自查报告
2014/10/28 职场文书
Pytorch 实现变量类型转换
2021/05/17 Python
springboot+VUE实现登录注册
2021/05/27 Vue.js
 分享一个Python 遇到数据库超好用的模块
2022/04/06 Python