浅谈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怎么把&amp;字符换成&quot;&amp;amp:&quot;
Oct 19 Javascript
JavaScript面向对象之静态与非静态类
Feb 03 Javascript
js选取多个或单个元素的实现代码(用class)
Aug 22 Javascript
基于JS实现导航条flash导航条
Jun 17 Javascript
利用jsonp跨域调用百度js实现搜索框智能提示
Aug 24 Javascript
js中利用cookie实现记住密码功能
Aug 20 Javascript
JS定时器实现数值从0到10来回变化
Dec 09 Javascript
bootstrap如何让dropdown menu按钮式下拉框长度一致
Apr 10 Javascript
原生js jquery ajax请求以及jsonp的调用方法
Aug 04 jQuery
一步步教你利用webpack如何搭一个vue脚手架(超详细讲解和注释)
Jan 08 Javascript
vue src动态加载请求获取图片的方法
Oct 17 Javascript
Angular5整合富文本编辑器TinyMCE的方法(汉化+上传)
May 26 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基于mssql扩展远程连接MSSQL的简单实现方法
2016/10/08 PHP
php arsort 数组降序排序详细介绍
2016/11/17 PHP
ThinkPhP+Apache+PHPstorm整合框架流程图解
2020/11/23 PHP
枚举JavaScript对象的函数
2006/12/22 Javascript
获取客户端电脑日期时间js代码(jquery)
2012/09/12 Javascript
原生JS和JQuery动态添加、删除表格行的方法
2015/05/28 Javascript
浅谈JSON.parse()和JSON.stringify()
2015/07/14 Javascript
jquery实现鼠标经过显示下划线的渐变下拉菜单效果代码
2015/08/24 Javascript
使用jQuery判断Div是否在可视区域的方法 判断div是否可见
2016/02/17 Javascript
AngularJS学习笔记之依赖注入详解
2016/05/16 Javascript
angularJS 如何读写缓冲的方法(推荐)
2016/08/06 Javascript
js获取页面引用的css样式表中的属性值方法(推荐)
2016/08/19 Javascript
老生常谈javascript的类型转换
2016/10/12 Javascript
js导出excel文件的简洁方法(推荐)
2016/11/02 Javascript
JavaScript中EventLoop介绍
2018/01/22 Javascript
Node.js中你不可不精的Stream(流)
2018/06/08 Javascript
JS双向链表实现与使用方法示例(增加一个previous属性实现)
2019/01/31 Javascript
深入浅析vue全局环境变量和模式
2020/04/28 Javascript
Vue ​v-model相关知识总结
2021/01/28 Vue.js
Python线程中对join方法的运用的教程
2015/04/09 Python
python比较两个列表大小的方法
2015/07/11 Python
使用python 将图片复制到系统剪贴中
2019/12/13 Python
解决Django no such table: django_session的问题
2020/04/07 Python
Python使用Chrome插件实现爬虫过程图解
2020/06/09 Python
Java语言程序设计测试题改错题部分
2014/07/22 面试题
光信息科学与技术专业职业生涯规划
2014/03/13 职场文书
小学数学教研活动总结
2014/07/01 职场文书
国际商务英语专业求职信
2014/07/08 职场文书
大一工商管理职业生涯规划:有梦最美,行动相随
2014/09/18 职场文书
教师个人工作总结范文2014
2014/11/10 职场文书
2014年宣传工作总结
2014/11/18 职场文书
汽车销售助理岗位职责
2015/04/14 职场文书
普希金的诗歌赏析(3首)
2019/08/20 职场文书
创业计划书之干洗店
2019/09/10 职场文书
MySQL学习总结-基础架构概述
2021/04/05 MySQL
Java 泛型详解(超详细的java泛型方法解析)
2021/07/02 Java/Android