浅谈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 相关文章推荐
Discuz! 6.1_jQuery兼容问题
Sep 23 Javascript
JQuery 动态扩展对象之另类视角
May 25 Javascript
JavaScript实现将xml转换成html table表格的方法
Apr 17 Javascript
jQuery插件scroll实现无缝滚动效果
Apr 27 Javascript
JavaScript根据CSS的Media Queries来判断浏览设备的方法
May 10 Javascript
浅析JavaScript中命名空间namespace模式
Jun 22 Javascript
深入理解bootstrap框架之入门准备
Oct 09 Javascript
浅谈JavaScript的函数及作用域
Dec 30 Javascript
Vue中添加过渡效果的方法
Mar 16 Javascript
BootStrap 表单控件之单选按钮水平排列
May 23 Javascript
JS查找孩子节点简单示例
Jul 25 Javascript
解决Can't find variable: SockJS vue项目的问题
Sep 22 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 如何利用phpexcel导入数据库
2013/08/24 PHP
PHP中时间加减函数strtotime用法分析
2017/04/26 PHP
PHP使用pdo实现事务处理操作示例
2018/09/05 PHP
js获取单选按钮的数据
2006/11/27 Javascript
javascript学习笔记(九)javascript中的原型(prototype)及原型链的继承方式
2011/04/12 Javascript
JavaScript实现自己的DOM选择器原理及代码
2013/03/04 Javascript
js复制到剪切板的实例方法
2013/06/28 Javascript
js 单击式的下拉菜单效果实例
2013/08/13 Javascript
JS、jquery实现几分钟前、几小时前、几天前等时间差显示效果的代码实例分享
2014/04/11 Javascript
原生js实现fadein 和 fadeout淡入淡出效果
2014/06/05 Javascript
jquery实现图片按比例缩放示例
2014/07/01 Javascript
drag-and-drop实现图片浏览器预览
2015/08/06 Javascript
基于jQuery和hwSlider实现内容左右滑动切换效果附源码下载(一)
2016/06/22 Javascript
node+experss实现爬取电影天堂爬虫
2016/11/20 Javascript
JS使用插件cryptojs进行加密解密数据实例
2017/05/11 Javascript
基于 Bootstrap Datetimepicker 联动
2017/08/03 Javascript
JavaScript实现的原生态Tab标签页功能【兼容IE6】
2017/09/18 Javascript
Vue项目组件化工程开发实践方案
2018/01/09 Javascript
Angular6 写一个简单的Select组件示例
2018/08/20 Javascript
微信小程序学习笔记之目录结构、基本配置图文详解
2019/03/28 Javascript
PHP实现发送和接收JSON请求
2018/06/07 Python
Python自动发送邮件的方法实例总结
2018/12/08 Python
运用Python的webbrowser实现定时打开特定网页
2019/02/21 Python
简单了解python的一些位运算技巧
2019/07/13 Python
python实现ip地址查询经纬度定位详解
2019/08/30 Python
Python爬虫之Selenium库的使用方法
2021/01/03 Python
anello泰国官方网站:日本流行包包品牌
2019/08/08 全球购物
体育专业个人的求职信范文
2013/09/21 职场文书
数控技术专科生自我评价
2014/01/08 职场文书
人事行政专员岗位职责
2014/07/23 职场文书
终止或解除劳动合同及劳动关系的证明书
2014/10/06 职场文书
婚前协议书范本两则
2014/10/16 职场文书
期末个人总结范文
2015/02/13 职场文书
创业计划书之水果店
2019/07/18 职场文书
redis限流的实际应用
2021/04/24 Redis
Nginx中使用Lua脚本与图片的缩略图处理的实现
2022/03/18 Servers