浅谈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 相关文章推荐
获取URL地址中的文件名和参数的javascript代码
Sep 02 Javascript
jQuery Animation实现CSS3动画示例介绍
Aug 14 Javascript
浅析基于WEB前端页面的页面内容搜索的实现思路
Jun 10 Javascript
关于编写性能高效的javascript事件的技术
Nov 28 Javascript
多种JQuery循环滚动文字图片效果代码
Jun 23 Javascript
js控件Kindeditor实现图片自动上传功能
Jul 20 Javascript
JS实现的五级联动菜单效果完整实例
Feb 23 Javascript
JavaScript实现的仿新浪微博原生态输入字数即时检查功能【兼容IE6】
Sep 26 Javascript
Vue使用zTree插件封装树组件操作示例
Apr 25 Javascript
JavaScript交换变量的常用方法小结【4种方法】
May 07 Javascript
JS代码简洁方式之函数方法详解
Jul 28 Javascript
如何让vue长列表快速加载
Mar 29 Vue.js
深入理解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
短波收音机简介
2021/03/01 无线电
php获取url参数方法总结
2014/11/13 PHP
php+ajax无刷新分页实例详解
2015/12/07 PHP
ThinkPHP使用Ueditor的方法详解
2016/05/20 PHP
Yii2.0建立公共方法简单示例
2019/01/29 PHP
thinkphp5框架扩展redis类方法示例
2019/05/06 PHP
用最通俗易懂的代码帮助新手理解javascript闭包 推荐
2012/03/01 Javascript
javascript 函数及作用域总结介绍
2013/11/12 Javascript
JavaScript执行顺序详细介绍
2013/12/04 Javascript
QQ空间顶部折页撕开效果示例代码
2014/06/15 Javascript
jQuery中on绑定事件后引发的事件冒泡问题如何解决
2016/05/25 Javascript
D3.js实现文本的换行详解
2016/10/14 Javascript
JS如何设置iOS中微信浏览器的title
2016/11/22 Javascript
Angularjs中使用指令绑定点击事件的方法
2017/03/30 Javascript
BootStrap Select清除选中的状态恢复默认状态
2017/06/20 Javascript
详解Angular 开发环境搭建
2017/06/22 Javascript
javascript 产生随机数的几种方法总结
2017/09/26 Javascript
JavaScript实现左侧菜单效果
2017/12/14 Javascript
JQuery实现table中tr上移下移的示例(超简单)
2018/01/08 jQuery
react-native android状态栏的实现
2018/06/15 Javascript
微信小程序 SOTER 生物认证DEMO 指纹识别功能
2019/12/13 Javascript
教你用Type Hint提高Python程序开发效率
2016/08/08 Python
django允许外部访问的实例讲解
2018/05/14 Python
Python提取支付宝和微信支付二维码的示例代码
2019/02/15 Python
pytorch三层全连接层实现手写字母识别方式
2020/01/14 Python
Python如何实现小程序 无限求和平均
2020/02/18 Python
Kaufmann Mercantile官网:家居装饰、配件、户外及更多
2018/09/28 全球购物
是否可以从一个static方法内部发出对非static方法的调用?
2014/08/18 面试题
2014三八妇女节活动总结
2014/03/01 职场文书
小学毕业寄语大全
2014/04/03 职场文书
无毒社区工作方案
2014/05/23 职场文书
2014幼儿园小班工作总结
2014/11/10 职场文书
幸福家庭事迹材料
2014/12/20 职场文书
爱心捐款倡议书:点燃希望,传递温暖
2019/11/04 职场文书
win11无法添加打印机怎么办? 提示windows无法打开添加打印机的解决办法
2022/04/05 数码科技
Oracle删除归档日志及添加定时任务
2022/06/28 Oracle