浅谈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 嵌套函数指向this对象错误的解决方法
Mar 15 Javascript
node.js中的path.resolve方法使用说明
Dec 08 Javascript
jQuery中outerHeight()方法用法实例
Jan 19 Javascript
javascript实现删除前弹出确认框
Jun 04 Javascript
node.js学习之base64编码解码
Oct 21 Javascript
vue.js开发环境搭建教程
May 04 Javascript
VUE长按事件需求详解
Oct 18 Javascript
基于vue组件实现猜数字游戏
May 28 Javascript
完美解决axios跨域请求出错的问题
Feb 05 Javascript
Vue实现PopupWindow组件详解
Apr 28 Javascript
详解React中setState回调函数
Jun 14 Javascript
elementUI Vue 单个按钮显示和隐藏的变换功能(两种方法)
Sep 04 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
二次元帅气男生排行榜,只想悄悄收藏系列
2020/03/04 日漫
长波有什么东西
2021/03/01 无线电
PHP实现把数字ID转字母ID
2013/08/12 PHP
TNC vs BOOM BO3 第二场2.13
2021/03/10 DOTA
jQuery获得IE版本不准确webbrowser的解决方法
2014/02/23 Javascript
jQuery队列操作方法实例
2014/06/11 Javascript
jQuery+ajax实现动态执行脚本的方法
2015/01/27 Javascript
基于jQuery实现点击最后一行实现行自增效果的表格
2016/01/12 Javascript
Javascript中常用的检测方法小结
2016/10/08 Javascript
纯原生js实现table表格的增删
2017/01/05 Javascript
React学习笔记之高阶组件应用
2018/06/02 Javascript
ajax与jsonp的区别及用法
2018/10/16 Javascript
Vue2.x通用条件搜索组件的封装及应用详解
2019/05/28 Javascript
微信小程序mpvue点击按钮获取button值的方法
2019/05/29 Javascript
微信小程序swiper禁止用户手动滑动代码实例
2019/08/23 Javascript
详解elementui之el-image-viewer(图片查看器)
2019/08/30 Javascript
Vue使用路由钩子拦截器beforeEach和afterEach监听路由
2020/11/16 Javascript
[56:29]Secret vs Optic 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
Python命令行参数解析模块optparse使用实例
2015/04/13 Python
使用python生成杨辉三角形的示例代码
2018/08/29 Python
python+Django+pycharm+mysql 搭建首个web项目详解
2019/11/29 Python
Django接收照片储存文件的实例代码
2020/03/07 Python
Python selenium自动化测试模型图解
2020/04/15 Python
django在开发中取消外键约束的实现
2020/05/20 Python
浅析canvas元素的html尺寸和css尺寸对元素视觉的影响
2019/07/22 HTML / CSS
Canvas在超级玛丽游戏中的应用详解
2021/02/06 HTML / CSS
澳大利亚设计的优质鞋类和适合澳大利亚生活方式的服装:Rivers
2019/04/23 全球购物
巴西儿童时尚购物网站:Dinda
2019/08/14 全球购物
如何唤起类中的一个方法
2013/11/29 面试题
学术会议邀请函范文
2014/01/22 职场文书
旅游节目策划方案
2014/05/26 职场文书
销售业务员岗位职责
2015/02/13 职场文书
嘉年华活动新闻稿
2015/07/17 职场文书
消防安全主题班会
2015/08/12 职场文书
检讨书范文
2019/04/16 职场文书
创业计划书之o2o水果店
2019/08/30 职场文书