浅谈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中cookie的使用详细分析
May 28 Javascript
JavaScript/jQuery 表单美化插件小结
Feb 14 Javascript
Javascript实现动态菜单添加的实例代码
Jul 05 Javascript
Bootstrap 轮播(Carousel)插件
Dec 26 Javascript
jQuery实现简易的输入框字数计数功能示例
Jan 16 Javascript
vue删除html内容的标签样式实例
Sep 13 Javascript
Vue.js 图标选择组件实践详解
Dec 03 Javascript
后台使用freeMarker和前端使用vue的方法及遇到的问题
Jun 13 Javascript
javascript中this的用法实践分析
Jul 29 Javascript
JavaScript实现音乐导航效果
Nov 19 Javascript
JavaScript实现切换多张图片
Jan 27 Javascript
详解vue中v-for的key唯一性
May 15 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
如何将数据从文本导入到mysql
2006/10/09 PHP
php 中英文语言转换类代码
2011/08/11 PHP
yii2.0实现创建简单widgets示例
2016/07/18 PHP
求得div 下 img的src地址的js代码
2007/02/28 Javascript
myFocus slide3D v1.1.0 使用方法与下载
2011/01/12 Javascript
jQuery的:parent选择器定义和用法
2014/07/01 Javascript
原生js事件的添加和删除的封装
2014/07/01 Javascript
JavaScript将取代AppleScript?
2014/09/18 Javascript
javascript折半查找详解
2015/01/26 Javascript
javascript实现日期按月份加减
2015/05/15 Javascript
JavaScript中Date.toSource()方法的使用教程
2015/06/12 Javascript
JavaScript学习笔记--常用的互动方法
2016/12/07 Javascript
详解AngularJS用Interceptors来统一处理HTTP请求和响应
2017/06/08 Javascript
微信分享调用jssdk实例
2017/06/08 Javascript
深入理解jquery的$.extend()、$.fn和$.fn.extend()
2017/07/08 jQuery
详解vue项目首页加载速度优化
2017/10/18 Javascript
使用JS获取页面上的所有标签
2018/10/18 Javascript
前端插件之Bootstrap Dual Listbox使用教程
2019/07/23 Javascript
JS操作json对象key、value的常用方法分析
2019/10/29 Javascript
node.js使用http模块创建服务器和客户端完整示例
2020/02/10 Javascript
vue打开新窗口并实现传参的图文实例
2021/03/04 Vue.js
Python中is与==判断的区别
2017/03/28 Python
Python实现获取汉字偏旁部首的方法示例【测试可用】
2018/12/18 Python
python pytest进阶之xunit fixture详解
2019/06/27 Python
python+selenium定时爬取丁香园的新型冠状病毒数据并制作出类似的地图(部署到云服务器)
2020/02/09 Python
Python list和str互转的实现示例
2020/11/16 Python
基于HTML5 Canvas 实现商场监控实例详解
2017/11/20 HTML / CSS
西班牙灯具网上商店:Lampara.es
2018/06/05 全球购物
出纳担保书范文
2014/04/02 职场文书
主题实践活动总结
2014/05/08 职场文书
初中作文评语集锦
2014/12/25 职场文书
2015年电厂工作总结范文
2015/05/13 职场文书
给原生html中添加水印遮罩层的实现示例
2021/04/02 Javascript
Python编程super应用场景及示例解析
2021/10/05 Python
Android移动应用开发指南之六种布局详解
2022/09/23 Java/Android
Mysql如何查看是否使用到索引
2022/12/24 MySQL