浅谈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 showModalDialog,open取得父窗口的方法
Mar 10 Javascript
使用 Node.js 做 Function Test实现方法
Oct 25 Javascript
jquery获取html元素的绝对位置和相对位置的方法
Jun 20 Javascript
JavaScript中Object.prototype.toString方法的原理
Feb 24 Javascript
AngularJS ng-template寄宿方式用法分析
Nov 07 Javascript
Bootstrap基本组件学习笔记之进度条(15)
Dec 08 Javascript
详解Angular的内置过滤器和自定义过滤器【推荐】
Dec 26 Javascript
JavaScript字符串对象
Jan 14 Javascript
JQuery查找子元素find()和遍历集合each的方法总结
Mar 07 Javascript
详解webpack 打包文件体积过大解决方案(code splitting)
Apr 10 Javascript
vue移动端项目缓存问题实践记录
Oct 29 Javascript
解决vuex数据页面刷新后初始化操作
Jul 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实现禁用IE和火狐的缓存问题
2012/12/03 PHP
显示程序执行时间php函数代码
2013/08/29 PHP
PHP中spl_autoload_register函数的用法总结
2013/11/07 PHP
php去除字符串中空字符的常用方法小结
2015/03/17 PHP
使用phpexcel类实现excel导入mysql数据库功能(实例代码)
2016/05/12 PHP
PHP读取大文件的几种方法介绍
2016/10/27 PHP
Laravel validate error处理,ajax,json示例
2019/10/25 PHP
Javascript实现的分页函数
2006/12/22 Javascript
jQuery 工具函数学习资料
2010/04/29 Javascript
基于jquery的使ListNav兼容中文首字拼音排序的实现代码
2011/07/10 Javascript
用js小类库获取浏览器的高度和宽度信息
2012/01/15 Javascript
JavaScript表达式:URL 协议介绍
2013/03/10 Javascript
document.documentElement的一些使用技巧
2013/04/18 Javascript
JS.GetAllChild(element,deep,condition)使用介绍
2013/09/21 Javascript
jQuery+PHP+MySQL二级联动下拉菜单实例讲解
2015/10/27 Javascript
Bootstrap3 图片(响应式图片&amp;图片形状)
2017/01/04 Javascript
在Vue组件中使用 TypeScript的方法
2018/02/28 Javascript
Layui点击图片弹框预览的实现方法
2019/09/16 Javascript
在Django的模型中添加自定义方法的示例
2015/07/21 Python
理解python中生成器用法
2017/12/20 Python
Python Print实现在输出中插入变量的例子
2019/12/25 Python
Python使用pickle进行序列化和反序列化的示例代码
2020/09/22 Python
在Pycharm中安装Pandas库方法(简单易懂)
2021/02/20 Python
css3制作动态进度条以及附加jQuery百分比数字显示
2012/12/13 HTML / CSS
css3选择器基本介绍
2014/12/15 HTML / CSS
佳能法国商店:Canon法国
2019/02/14 全球购物
减负增效提质方案
2014/05/23 职场文书
企业读书活动总结
2014/06/30 职场文书
党员国庆节演讲稿范文2014
2014/09/21 职场文书
公司搬迁通知
2015/04/20 职场文书
听证会主持词
2015/07/03 职场文书
2016年父亲节寄语
2015/12/04 职场文书
如何才能写好调研报告?
2019/07/03 职场文书
javascript函数式编程基础
2021/09/15 Javascript
css常用字体属性与背景属性介绍
2022/02/28 HTML / CSS
SpringBoot前端后端分离之Nginx服务器下载安装过程
2022/08/14 Servers