浅谈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 相关文章推荐
不能再简单的无闪刷新验证码原理很简单
Nov 05 Javascript
js tab 选项卡
Apr 26 Javascript
20个最新的jQuery插件
Jan 13 Javascript
js 立即调用的函数表达式如何写
Jan 12 Javascript
JavaScript基础知识点归纳(推荐)
Jul 09 Javascript
利用React-router+Webpack快速构建react程序
Oct 27 Javascript
不得不看之JavaScript构造函数及new运算符
Aug 21 Javascript
使用原生js封装的ajax实例(兼容jsonp)
Oct 12 Javascript
vue init webpack myproject构建项目 ip不能访问的解决方法
Mar 20 Javascript
微信小程序 冒泡事件原理解析
Sep 27 Javascript
JS实现联想、自动补齐国家或地区名称的功能
Jul 07 Javascript
jQuery实现带进度条的轮播图
Sep 13 jQuery
深入理解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中使用__autoload()自动加载未定义类的实现代码
2013/02/06 PHP
sql注入与转义的php函数代码
2013/06/17 PHP
基于preg_match_all采集后数据处理的一点心得笔记(编码转换和正则匹配)
2014/01/31 PHP
php计划任务之ignore_user_abort函数实现方法
2015/01/08 PHP
使用Appcan客户端自动更新PHP版本号(全)
2015/07/31 PHP
php-fpm中max_children的配置
2019/03/15 PHP
jquery的ajax请求全面了解
2013/03/20 Javascript
关于jQuery新的事件绑定机制on()的使用技巧
2013/04/26 Javascript
jQuery调用ajax请求的常见方法汇总
2015/03/24 Javascript
详解JavaScript中常用的函数类型
2015/11/18 Javascript
详解AngularJS ng-class样式切换
2017/06/27 Javascript
微信通过页面(H5)直接打开本地app的解决方法
2017/09/09 Javascript
vue mintui-Loadmore结合实现下拉刷新和上拉加载示例
2017/10/12 Javascript
jQuery实现模糊搜索功能的方法分析
2018/06/29 jQuery
ant-design-vue中的select选择器,对输入值的进行筛选操作
2020/10/24 Javascript
小程序中手机号识别的示例
2020/12/14 Javascript
python装饰器decorator介绍
2014/11/21 Python
python利用OpenCV2实现人脸检测
2020/04/16 Python
Python实现学生成绩管理系统
2020/04/05 Python
python定向爬取淘宝商品价格
2018/02/27 Python
python opencv实现运动检测
2018/07/10 Python
在python中pandas的series合并方法
2018/11/12 Python
Python小白必备的8个最常用的内置函数(推荐)
2019/04/03 Python
python实现大文件分割与合并
2019/07/22 Python
用python写PDF转换器的实现
2020/10/29 Python
浅谈盘点5种基于Python生成的个性化语音方法
2021/02/05 Python
顶级宝石首饰网络零售商:Angara
2016/10/25 全球购物
有趣的睡衣和礼物:LazyOne
2019/11/27 全球购物
竞职演讲稿范文
2014/01/11 职场文书
违反课堂纪律检讨书
2014/01/19 职场文书
怎样写好创业计划书的内容
2014/02/06 职场文书
优秀食品类广告词
2014/03/19 职场文书
爱岗敬业演讲稿
2014/05/05 职场文书
学习走群众路线心得体会
2014/11/05 职场文书
学校党风廉政建设调研报告
2015/01/01 职场文书
音乐剧猫观后感
2015/06/04 职场文书