浅谈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 相关文章推荐
jQuery 操作下拉列表框实现代码
Feb 22 Javascript
jQuery 点击图片跳转上一张或下一张功能的实现代码
Mar 12 Javascript
纯javascript移动优先的幻灯片效果
Nov 02 Javascript
Angular和百度地图的结合实例代码
Oct 19 Javascript
原生js实现手风琴功能(支持横纵向调用)
Jan 13 Javascript
jQuery实现一个简单的验证码功能
Jun 26 jQuery
AngularJs点击状态值改变背景色的实例
Dec 18 Javascript
在小程序中集成redux/immutable/thunk第三方库的方法
Aug 12 Javascript
100行代码实现一个vue分页组功能
Nov 06 Javascript
JavaScript模板引擎应用场景及实现原理详解
Dec 14 Javascript
JS html事件冒泡和事件捕获操作示例
May 01 Javascript
vue项目初始化到登录login页面的示例
Oct 31 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 判断网页是否是utf8编码的方法
2014/06/06 PHP
php版微信支付api.mch.weixin.qq.com域名解析慢原因与解决方法
2016/10/12 PHP
Thinkphp5行为使用方法汇总
2017/12/21 PHP
Laravel (Lumen) 解决JWT-Auth刷新token的问题
2019/10/24 PHP
雄兵连第三季海报曝光,艾妮熙德成主角,蔷薇新造型
2021/03/09 国漫
使用documentElement正确取得当前可见区域的大小
2014/07/25 Javascript
iScroll中事件点击触发两次解决方案
2015/03/11 Javascript
浅谈JSON.parse()和JSON.stringify()
2015/07/14 Javascript
将List对象列表转换成JSON格式的类实现方法
2016/07/04 Javascript
BootStrap入门教程(一)之可视化布局
2016/09/19 Javascript
Bootstrap入门教程一Hello Bootstrap初识
2017/03/02 Javascript
关于JavaScript中forEach和each用法浅析
2017/07/27 Javascript
微信小程序实现倒计时调用相机自动拍照功能
2018/06/10 Javascript
详解如何从零开始搭建Express+Vue开发环境
2018/07/17 Javascript
vue elementUI使用tabs与导航栏联动
2019/06/21 Javascript
JavaScript实现的开关灯泡点击切换特效示例
2019/07/08 Javascript
layui2.0使用table+laypage实现真分页
2019/07/27 Javascript
pageGroup.js实现分页功能
2019/07/27 Javascript
layui按条件隐藏表格列的实例
2019/09/19 Javascript
微信小程序地图绘制线段并且测量(实例代码)
2020/01/02 Javascript
Windows下安装python2和python3多版本教程
2017/03/30 Python
Python实现霍夫圆和椭圆变换代码详解
2018/01/12 Python
python 去除txt文本中的空格、数字、特定字母等方法
2018/07/24 Python
使用python读取.text文件特定行的数据方法
2019/01/28 Python
python3.7 使用pymssql往sqlserver插入数据的方法
2019/07/08 Python
python获取Pandas列名的几种方法
2019/08/07 Python
CSS3实现水平居中、垂直居中、水平垂直居中的实例代码
2020/02/27 HTML / CSS
英国家具、照明、家居用品网上商店:Wayfair.co.uk
2020/02/13 全球购物
如何现实servlet的单线程模式
2014/08/05 面试题
急诊科护士自我鉴定
2013/10/14 职场文书
人事部主管岗位职责
2013/12/26 职场文书
法制宣传实施方案
2014/03/13 职场文书
学校安全生产月活动总结
2014/07/05 职场文书
《时代广场的蟋蟀》读后感:真挚友情,温暖世界!
2020/01/08 职场文书
小程序实现文字循环滚动动画
2021/06/14 Javascript
javascript进阶篇深拷贝实现的四种方式
2022/07/07 Javascript