浅谈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 相关文章推荐
jquery下json数组的操作实现代码
Aug 09 Javascript
jQuery中$.fn的用法示例介绍
Nov 05 Javascript
javascript中普通函数的使用介绍
Dec 19 Javascript
ff chrome和ie下全局动态定位的异同及全局高度的取法
Jun 30 Javascript
js+jquery常用知识点汇总
Mar 03 Javascript
JS基于面向对象实现的拖拽功能示例
Dec 20 Javascript
JavaScript实现简单评论功能
Aug 17 Javascript
Vue.js 事件修饰符的使用教程
Nov 01 Javascript
微信实现自动跳转到用其他浏览器打开指定APP下载
Feb 15 Javascript
vue项目中在可编辑div光标位置插入内容的实现代码
Jan 07 Javascript
微信小程序拖拽排序列表的示例代码
Jul 08 Javascript
JS实现简单移动端鼠标拖拽
Jul 23 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
Windows Apache2.2.11及Php5.2.9-1的安装与配置方法
2009/06/08 PHP
Yii隐藏URL中index.php的方法
2016/07/12 PHP
PHP图形计数器程序显示网站用户浏览量
2016/07/20 PHP
Yii2主题(Theme)用法详解
2016/07/23 PHP
Laravel 自定命令以及生成文件的例子
2019/10/23 PHP
gearman中任务的优先级和返回状态实例分析
2020/02/27 PHP
javascript下过滤数组重复值的代码
2007/09/10 Javascript
腾讯与新浪的通过IP地址获取当前地理位置(省份)的接口
2010/07/26 Javascript
appendChild() 或 insertBefore()使用与区别介绍
2013/10/11 Javascript
jquery如何根据值设置默认的选中项
2014/03/17 Javascript
jquery删除数据记录时的弹出提示效果
2014/05/06 Javascript
抛弃Nginx使用nodejs做反向代理服务器
2014/07/17 NodeJs
js实现ArrayList功能附实例代码
2014/10/29 Javascript
ionic环境配置及问题详解
2017/06/27 Javascript
微信小程序 点击切换样式scroll-view实现代码实例
2019/10/11 Javascript
简单了解JavaScript作用域
2020/07/31 Javascript
[46:53]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
简单解析Django框架中的表单验证
2015/07/17 Python
详解Python的Django框架中inclusion_tag的使用
2015/07/21 Python
python简单实现操作Mysql数据库
2018/01/29 Python
使用Python设计一个代码统计工具
2018/04/04 Python
django Admin文档生成器使用详解
2019/07/22 Python
CSS3 仿微信聊天小气泡实例代码
2017/04/05 HTML / CSS
美国卡车、吉普车和SUV零件网站:4 Wheel Parts
2016/11/24 全球购物
俄罗斯在线水暖商店:Perfecto.ru
2019/10/25 全球购物
自我评价怎么写好呢?
2013/12/05 职场文书
医院总经理职责
2013/12/26 职场文书
电子商务系毕业生自荐信
2014/05/29 职场文书
建筑工地质量标语
2014/06/12 职场文书
大学社团招新的通讯稿
2014/09/10 职场文书
农村党支部书记党群众路线四风问题整改措施
2014/09/26 职场文书
小学音乐教师个人工作总结
2015/02/05 职场文书
工伤认定行政答辩状
2015/05/22 职场文书
MySQL表的增删改查基础教程
2021/04/07 MySQL
MySQL系列之八 MySQL服务器变量
2021/07/02 MySQL
html5 录制mp3音频支持采样率和比特率设置
2021/07/15 Javascript