Angular2服务中原始与引用类型属性变更检测差异问询
嘿,这个问题其实戳中了Angular变更检测的核心逻辑,以及JavaScript里原始类型和引用类型的本质差异——我来给你拆解得明明白白:
一、为什么Book数组(引用类型)的变更能被组件感知?
这背后是JavaScript引用类型的特性:
- 数组、自定义对象这类引用类型,变量存储的是它们在内存中的地址,而不是实际值。当你在服务里调用
this._books.push(book)时,并没有改变_books变量本身的内存地址,只是修改了这个地址指向的数组内容。 - 你的
ViewBooksComponent在构造函数里通过this.bookss = this.booksService.fetchBook()拿到的是这个数组的引用(也就是同一个内存地址)。所以当服务里修改数组内容时,组件里的bookss指向的还是同一个数组。 - Angular的变更检测会在特定时机(比如用户事件、路由切换、异步操作完成后)触发,遍历模板里的
*ngFor时会发现数组内容变了,自然就更新了视图。
结合你的代码来看:服务的fetchBook()直接返回了this._books本身,没有做数组拷贝,所以组件和服务共享同一个数组引用,数组内容的变更就能同步到组件视图。
二、为什么原始类型(addedNumber)的变更无法同步?
这是因为JavaScript的原始类型(Number、String、Boolean等)是值传递:
- 你的
ViewBooksComponent在构造函数里执行this.count = this.booksService.getAddCount()时,其实是把服务里addedNumber的当前值(初始是0)复制了一份给组件的count变量。这时候组件的count和服务的addedNumber是两个完全独立的变量,存在不同的内存位置。 - 当你在服务里把
addedNumber改成20时,组件的count还是原来的0——因为它只是个副本,和服务的变量没有绑定关系。再加上你用了RouterReuseStrategy确保组件构造函数只执行一次,组件不会主动去重新获取新的addedNumber值,所以视图里的{{count}}一直显示初始值。 - 对比
AddBooksComponent的情况:你在addBook()方法里主动重新调用了this.count = this.booksService.getAddCount(),所以它能拿到最新的20;但ViewBooksComponent没有这个触发逻辑,自然不会更新。
三、怎么解决原始类型属性的同步问题?
最推荐的方案是用RxJS的BehaviorSubject来实现响应式的状态同步,这也是Angular生态里的最佳实践:
第一步:修改BooksService,把原始类型包装成可观察对象
import { Injectable } from "@angular/core"; import { Book } from "../dto/book"; import { BehaviorSubject } from 'rxjs'; @Injectable() export class BooksService{ private _books:Book[]; // 用BehaviorSubject包装原始值,初始值设为0 private addedNumberSubject = new BehaviorSubject<Number>(0); // 暴露给组件的可观察对象(外部只能订阅,不能修改) addedNumber$ = this.addedNumberSubject.asObservable(); getAddCount():Number{ return this.addedNumberSubject.value; } constructor(){ this._books=[]; let book=new Book(1,"akriti"); this._books.push(book); } addBook(book:Book){ let matchFound=false; this._books.every(function(element){ if(element.id==book.id){ matchFound=true; return false; } else return true; }); if(!matchFound){ this._books.push(book); } // 发送新值给所有订阅者 this.addedNumberSubject.next(20); console.log("number: "+this.addedNumberSubject.value); } fetchBook():Book[]{ console.log("automatch"); return this._books; } }
第二步:修改ViewBooksComponent,订阅值的变更
import { Component, OnInit, OnDestroy } from "@angular/core"; import { Book } from "../dto/book"; import { BooksService } from "../service/books.service"; import { Subscription } from 'rxjs'; @Component({ templateUrl:'./view.books.component.html' }) export class ViewBooksComponent implements OnInit, OnDestroy{ private bookss:any; private count:Number; // 保存订阅对象,防止内存泄漏 private subscription: Subscription; constructor(private booksService:BooksService){ console.log("view books constructor"); this.bookss=this.booksService.fetchBook(); } ngOnInit() { // 订阅addedNumber的变更,每次值更新时同步到组件的count this.subscription = this.booksService.addedNumber$.subscribe(num => { this.count = num; }); } ngOnDestroy() { // 组件销毁时取消订阅 this.subscription.unsubscribe(); } }
这样修改后,不管服务里的addedNumber什么时候变更,ViewBooksComponent都会自动收到通知并更新视图,完美解决同步问题。
另外还有两种备选方案,但不推荐作为长期方案:
- 手动触发变更检测:注入
ChangeDetectorRef,在需要更新时调用detectChanges(),但会增加不必要的性能开销,不符合Angular的变更检测理念。 - 把原始类型包装成对象:比如把
addedNumber放到{ value: 0 }这样的对象里,组件拿到对象引用后,服务修改obj.value时组件能看到变化,但这种方式不够优雅,也不利于状态管理。
内容的提问来源于stack exchange,提问作者Akriti Mahajan
相关产品推荐
相关产品推荐

