浅谈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的读写操作示例详解
Apr 17 Javascript
利用jquery操作Radio方法小结
Oct 20 Javascript
jquery代码实现多选、不同分享功能
Jul 31 Javascript
jQuery 全选 全不选 事件绑定的实现代码
Jan 23 Javascript
vue.js加载新的内容(实例代码)
Jun 01 Javascript
如何理解Vue的作用域插槽的实现原理
Aug 19 Javascript
利用vue开发一个所谓的数独方法实例
Dec 21 Javascript
讲解vue-router之什么是编程式路由
May 28 Javascript
TypeScript基础入门教程之三重斜线指令详解
Oct 22 Javascript
详解javascript中的Error对象
Apr 25 Javascript
JQuery获得内容和属性方法解析
May 30 jQuery
前端vue+express实现文件的上传下载示例
Feb 18 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
PHP 数组遍历方法大全(foreach,list,each)
2010/06/30 PHP
第七章 php自定义函数实现代码
2011/12/30 PHP
PHP中“简单工厂模式”实例代码讲解
2012/09/04 PHP
Thinkphp实现MySQL读写分离操作示例
2014/06/25 PHP
如何让thinkphp在模型中自动完成session赋值小教程
2014/09/05 PHP
PHP获取数组最后一个值的2种方法
2015/01/21 PHP
PHP登录验证功能示例【用户名、密码、验证码、数据库、已登陆验证、自动登录和注销登录等】
2019/02/25 PHP
php ZipArchive实现多文件打包下载实例
2019/10/31 PHP
php+js实现的拖动滑块验证码验证表单操作示例【附源码下载】
2020/05/27 PHP
Jquery 常用方法经典总结
2010/01/28 Javascript
利用jQuery操作对象数组的实现代码
2011/04/27 Javascript
js控制web打印(局部打印)方法整理
2013/05/29 Javascript
AngularJS 作用域详解及示例代码
2016/08/17 Javascript
AngularJS bootstrap启动详解及实例代码
2016/09/14 Javascript
jQuery页面弹出框实现文件上传
2017/02/09 Javascript
BetterScroll 在移动端滚动场景的应用
2017/09/18 Javascript
vue2.0 兄弟组件(平级)通讯的实现代码
2018/01/15 Javascript
如何使node也支持从url加载一个module详解
2018/06/05 Javascript
js技巧之十几行的代码实现vue.watch代码
2018/06/09 Javascript
解决vue keep-alive 数据更新的问题
2018/09/21 Javascript
谈谈JavaScript中super(props)的重要性
2019/02/12 Javascript
分享一款超好用的JavaScript 打包压缩工具
2020/04/26 Javascript
JS模拟实现京东快递单号查询
2020/11/30 Javascript
[46:23]完美世界DOTA2联赛PWL S2 FTD vs Magma 第一场 11.20
2020/11/23 DOTA
Python2.7基于笛卡尔积算法实现N个数组的排列组合运算示例
2017/11/23 Python
python实现关键词提取的示例讲解
2018/04/28 Python
Python二进制串转换为通用字符串的方法
2018/07/23 Python
tensorflow入门:TFRecordDataset变长数据的batch读取详解
2020/01/20 Python
Python拼接字符串的7种方式详解
2020/03/19 Python
Pyinstaller 打包发布经验总结
2020/06/02 Python
2015最新学生自我评价范文
2015/03/03 职场文书
教师求职信怎么写
2015/03/20 职场文书
销售督导岗位职责
2015/04/10 职场文书
Go语言实现Snowflake雪花算法
2021/06/08 Golang
在redisCluster中模糊获取key方式
2021/07/09 Redis
一篇文章带你了解Python和Java的正则表达式对比
2021/09/15 Python