浅谈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 相关文章推荐
JS与框架页的操作代码
Jan 17 Javascript
JQuery对id中含有特殊字符的转义处理示例
Sep 06 Javascript
使用jQuery解决IE与FireFox下createElement方法的差异
Nov 14 Javascript
Jquery 动态生成表格示例代码
Dec 24 Javascript
Jquery中国地图热点效果-鼠标经过弹出提示层信息的简单实例
Feb 12 Javascript
display和visibility的区别示例介绍
Feb 26 Javascript
使用JavaScript判断手机浏览器是横屏还是竖屏问题
Aug 02 Javascript
JavaScript简单拖拽效果(1)
May 17 Javascript
通过V8源码看一个关于JS数组排序的诡异问题
Aug 14 Javascript
元素全屏的设置与监听实例
Nov 28 Javascript
Angularjs中date过滤器失效的问题及解决方法
Jul 06 Javascript
JSONP解决JS跨域问题的实现
May 25 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 自写函数代码 获取关键字 去超链接
2010/02/08 PHP
php实现上传图片生成缩略图示例
2014/04/13 PHP
php写app接口并返回json数据的实例(分享)
2017/05/20 PHP
JavaScript 自动分号插入(JavaScript synat:auto semicolon insertion)
2009/11/04 Javascript
JavaScript高级程序设计(第3版)学习笔记3 js简单数据类型
2012/10/11 Javascript
基于JavaScript 声明全局变量的三种方式详解
2013/05/07 Javascript
仿当当网淘宝网等主流电子商务网站商品分类导航菜单
2013/09/25 Javascript
jQuery获取当前对象标签名称的方法
2014/02/07 Javascript
JS实现可展开折叠层的鼠标拖曳效果
2015/10/09 Javascript
javascript时间排序算法实现活动秒杀倒计时效果
2021/01/28 Javascript
jQuery实现调整表格单列顺序完整实例
2016/06/20 Javascript
JS图片定时翻滚效果实现方法
2016/06/21 Javascript
JS小数转换为整数的方法分析
2017/01/07 Javascript
除Console.log()外更多的Javascript调试命令
2018/01/24 Javascript
vue.js学习笔记之v-bind和v-on解析
2018/05/03 Javascript
vue嵌套路由与404重定向实现方法分析
2018/05/04 Javascript
详解vue-router传参的两种方式
2018/09/10 Javascript
微信小程序实现保存图片到相册功能
2018/11/30 Javascript
[02:38]2018DOTA2亚洲邀请赛赛前采访-VGJ.T
2018/04/03 DOTA
对python中Librosa的mfcc步骤详解
2019/01/09 Python
Flask-WTF表单的使用方法
2019/07/12 Python
Python BeautifulSoup [解决方法] TypeError: list indices must be integers or slices, not str
2019/08/07 Python
Django框架安装方法图文详解
2019/11/04 Python
Python生成词云的实现代码
2020/01/14 Python
美国办公用品购物网站:Quill.com
2016/09/01 全球购物
材料化学应届生求职信
2013/10/09 职场文书
生物制药专业自我鉴定
2014/02/19 职场文书
2014年元旦感言
2014/03/06 职场文书
《泉水》教学反思
2014/04/11 职场文书
祖国在我心中演讲稿400字
2014/05/04 职场文书
欢迎新生标语
2014/10/06 职场文书
党政领导班子群众路线对照检查材料
2014/10/26 职场文书
学校法制宣传日活动总结
2014/11/01 职场文书
法院执行局工作总结
2015/08/11 职场文书
经销商会议开幕词
2016/03/04 职场文书
2016年助残日旅游活动总结
2016/04/01 职场文书