浅谈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学习笔记(五) Array 数组类型介绍
Jun 19 Javascript
express的中间件basicAuth详解
Dec 04 Javascript
jQuery简单实现验证邮箱格式
Jul 15 Javascript
jQuery编程中的一些核心方法简介
Aug 14 Javascript
JavaScript操作HTML DOM节点的基础教程
Mar 11 Javascript
jQuery轮播图效果精简版完整示例
Sep 04 Javascript
详解Vue.js——60分钟组件快速入门(上篇)
Dec 05 Javascript
基于jQuery制作小图标上下滑动特效
Jan 18 Javascript
jQuery使用JSONP实现跨域获取数据的三种方法详解
May 04 jQuery
详解Vue.js分发之作用域槽
Jun 13 Javascript
微信小程序云开发之使用云存储
May 17 Javascript
vue实现路由监听和参数监听
Oct 29 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
ueditor 1.2.6 使用方法说明
2013/07/24 PHP
PHP实现微信公众平台音乐点播
2014/03/20 PHP
关于PHP 如何用 curl 读取 HTTP chunked 数据
2016/02/26 PHP
jQuery使用手册之一
2007/03/24 Javascript
jquery indexOf使用方法
2013/08/19 Javascript
JS实现侧悬浮浮动实例代码
2013/11/29 Javascript
JavaScript中一个奇葩的IE浏览器判断方法
2014/04/16 Javascript
Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理)
2015/01/23 Javascript
JavaScript DOM 对象深入了解
2016/07/20 Javascript
浅谈js常用内置方法和对象
2016/09/24 Javascript
Vue键盘事件用法总结
2017/04/18 Javascript
Javascript实现页面滚动时导航智能定位
2017/05/06 Javascript
angular bootstrap timepicker TypeError提示怎么办
2017/06/13 Javascript
nodejs创建简易web服务器与文件读写的实例
2017/09/07 NodeJs
微信小程序使用video组件播放视频功能示例【附源码下载】
2017/12/08 Javascript
jQuery实现定时隐藏对话框的方法分析
2018/02/12 jQuery
Express本地测试HTTPS的示例代码
2018/06/06 Javascript
微信小程序实现定位及到指定位置导航的示例代码
2019/08/20 Javascript
JS几个常用的函数和对象定义与用法示例
2020/01/15 Javascript
ssm+vue前后端分离框架整合实现(附源码)
2020/07/08 Javascript
[27:53]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS iG
2014/05/26 DOTA
[48:27]EG vs Liquid 2018国际邀请赛淘汰赛BO3 第二场 8.25
2018/08/29 DOTA
python根据给定文件返回文件名和扩展名的方法
2015/03/27 Python
Python中文件操作简明介绍
2015/04/13 Python
探究Python中isalnum()方法的使用
2015/05/18 Python
CentOS中升级Python版本的方法详解
2017/07/10 Python
Python算法之图的遍历
2017/11/16 Python
使用pandas实现csv/excel sheet互相转换的方法
2018/12/10 Python
Python判断字符串是否xx开始或结尾的示例
2019/08/08 Python
python 使用pdfminer3k 读取PDF文档的例子
2019/08/27 Python
Python流程控制 if else实现解析
2019/09/02 Python
python GUI库图形界面开发之PyQt5表格控件QTableView详细使用方法与实例
2020/03/01 Python
2014年银行员工年终自我评价
2014/09/19 职场文书
债务纠纷代理词
2015/05/25 职场文书
Golang中异常处理机制详解
2021/06/08 Golang
「玫瑰之王的葬礼」舞台剧主视觉图公开
2022/03/21 日漫