搜尋
首頁web前端js教程詳細解讀Angular系列中變化檢測問題

詳細解讀Angular系列中變化檢測問題

Jun 04, 2018 pm 02:39 PM
angular變化

這篇文章主要介紹了詳解Angular系列之變化檢測(Change Detection),現在分享給大家,也給大家做個參考。

概述

簡單來說變化偵測就是Angular用來偵測視圖與模型之間綁定的值是否發生了改變,當檢測到模型中綁定的值發生變化時,則同步到視圖上,反之,當檢測到視圖上綁定的值發生變化時,則回調對應的綁定函數。

什麼情況會造成變化偵測?

總結起來, 主要有以下幾種情況可能也改變資料:

  1. 使用者輸入操作,例如點擊,提交等

  2. 請求服務端資料(XHR)

  3. #定時事件,例如setTimeout,setInterval

上述三種情況都有一個共同點,就是這些導致綁定值改變的事件都是非同步發生的。如果這些非同步的事件在發生時能夠通知到Angular框架,那麼Angular框架就能及時的偵測到變化。

左邊表示將要執行的程式碼,這裡的stack表示Javascript的運行棧,而webApi則是瀏覽器中提供的一些Javascript的API,TaskQueue表示Javascript中任務佇列,因為Javascript是單執行緒的,非同步任務在任務佇列中執行。

具體來說,非同步執行的運行機制如下:

  1. 所有同步任務都在主執行緒上執行,形成一個執行堆疊(execution context stack)。

  2. 主執行緒之外,還存在一個"任務隊列"(task queue)。只要非同步任務有了運行結果,就在"任務隊列"之 中放置一個事件。

  3. 一旦"執行堆疊"中的所有同步任務執行完畢,系統就會讀取"任務佇列",看看裡面有哪些事件。那些對應的非同步任務,於是結束等待狀態,進入執行棧,開始執行。

  4. 主執行緒不斷重複上面的第三步。

當上述程式碼在Javascript中執行時,首先func1 進入運行棧,func1執行完畢後,setTimeout進入運行棧,執行setTimeout過程中將回呼函數cb 加入任務佇列,接著setTimeout出棧,接著執行func2函數,func2函數執行完畢時,運行棧為空,接著任務佇列中cb 進入運行棧得到執行。可以看出非同步任務首先會進入任務佇列,當執行堆疊中的同步任務都執行完畢時,非同步任務進入運行堆疊會執行。如果這些非同步的任務執行前與執行後能提供一些鉤子函數,透過這些鉤子函數,Angular便能獲知非同步任務的執行。

angular2 取得變更通知

那麼問題來了,angular2是如何知道資料改變了?又是如何知道需要修改DOM的位置,準確的最小範圍的修改DOM呢?沒錯,盡可能小的範圍修改DOM,因為操作DOM對於性能來說可是一件奢侈品。

在AngularJS中是由程式碼$scope.$apply()或$scope.$digest觸發,而Angular接入了ZoneJS,由它監聽了Angular所有的非同步事件。

ZoneJS是怎麼做到的呢?

其實Zone有一個叫猴子補丁的東西。在Zone.js運行時,就會為這些非同步事件做一層代理程式包裹,也就是說Zone.js運行後,呼叫setTimeout、addEventListener等瀏覽器非同步事件時,不再是呼叫原生的方法,而是被猴子補丁包裝過後的代理方法。代理程式裡setup了鉤子函數, 透過這些鉤子函數, 可以方便的進入非同步任務執行的上下文.

//以下是Zone.js启动时执行逻辑的抽象代码片段
function zoneAwareAddEventListener() {...}
function zoneAwareRemoveEventListener() {...}
function zoneAwarePromise() {...}
function patchTimeout() {...}
window.prototype.addEventListener=zoneAwareAddEventListener;
window.prototype.removeEventListener=zoneAwareRemoveEventListener;
window.prototype.promise = zoneAwarePromise;
window.prototype.setTimeout = patchTimeout;

##變化檢測的過程

Angular的核心是組件化,組件的嵌套會使得最終形成一棵組件樹。 Angular的變化偵測可以分元件進行,每個Component都對應有一個changeDetector,我們可以在Component中透過依賴注入來取得到changeDetector。而我們的多個Component是一個樹狀結構的組織,由於一個Component對應一個changeDetector,那麼changeDetector之間同樣是一個樹狀結構的組織.

另外,Angular的資料流是自頂而下,從父元件到子元件單向流動。單向資料流向保證了高效、可預測的變化檢測。儘管檢查了父元件之後,子元件可能會改變父元件的資料使得父元件需要再次被檢查,這是不被推薦的資料處理方式。在開發模式下,Angular會進行二次檢查,如果出現上述情況,二次檢查就會報錯:Expression Changed After It Has Been Checked Error。而在生產環境中,髒檢查只會執行一次。

相比之下,AngularJS采用的是双向数据流,错综复杂的数据流使得它不得不多次检查,使得数据最终趋向稳定。理论上,数据可能永远不稳定。AngularJS给出的策略是,脏检查超过10次,就认为程序有问题,不再进行检查。

变化检测策略

Angular有两种变化检测策略。Default是Angular默认的变化检测策略,也就是上述提到的脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查,。另一种更加高效的变化检测方式:OnPush。OnPush策略,就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。

defalut 策略

main.component.ts

@Component({
 selector: 'app-root',
 template: `
 <h1 id="变更检测策略">变更检测策略</h1>
 <p>{{ slogan }}</p>
 <button type="button" (click)="changeStar()"> 改变明星属性
 </button>
 <button type="button" (click)="changeStarObject()">
   改变明星对象
 </button>
 <movie [title]="title" [star]="star"></movie>`,
})
export class AppComponent {
 slogan: string = &#39;change detection&#39;;
 title: string = &#39;default 策略&#39;;
 star: Star = new Star(&#39;周&#39;, &#39;杰伦&#39;);
 changeStar() {
  this.star.firstName = &#39;吴&#39;;
  this.star.lastName = &#39;彦祖&#39;;
 }
 changeStarObject() {
  this.star = new Star(&#39;刘&#39;, &#39;德华&#39;);
 } 
}

movie.component.ts

@Component({
 selector: &#39;movie&#39;,
 styles: [&#39;p {border: 1px solid black}&#39;],
 template: `
<p>
<h3 id="nbsp-title-nbsp">{{ title }}</h3>
<p>
<label>Star:</label>
<span>{{star.firstName}} {{star.lastName}}</span>
</p>
</p>`,

})
export class MovieComponent {
 @Input() title: string;
 @Input() star;
}

上面代码中, 当点击第一个按钮改变明星属性时,依次对slogan, title, star三个属性进行检测, 此时三个属性都没有变化, star没有发生变化,是因为实质上在对star检测时只检测star本身的引用值是否发生了改变,改变star的属性值并未改变star本身的引用,因此是没有发生变化。

而当我们点击第二个按钮改变明星对象时 ,重新new了一个 star ,这时变化检测才会检测到 star发生了改变。

然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

OnPush策略

与上面代码相比, 只在movie.component.ts中的@component中增加了一行代码:

changeDetection:ChangeDetectionStrategy.OnPush
此时, 当点击第一个按钮时, 检测到star没有发生变化, ok,变化检测到此结束, 不会进入到子组件中, 视图不会发生变化.

当点击第二个按钮时,检测到star发生了变化, 然后变化检测进入到子组件中,检测到star.firstName和star.lastName发生了变化, 然后更新视图.

所以,当你使用了OnPush检测机制时,在修改一个绑定值的属性时,要确保同时修改到了绑定值本身的引用。但是每次需要改变属性值的时候去new一个新的对象会很麻烦,immutable.js 你值得拥有!

变化检测对象引用

通过引用变化检测对象ChangeDetectorRef,可以手动去操作变化检测。我们可以在组件中的通过依赖注入的方式来获取该对象:

constructor(
  private changeRef:ChangeDetectorRef
 ){}

变化检测对象提供的方法有以下几种:

  1. markForCheck() - 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。

  2. detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。

  3. reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测

  4. detectChanges() - 从该组件到各个子组件执行一次变化检测

OnPush策略下手动发起变化检测

组件中添加事件改变输入属性

在上面代码movie.component.ts中修改如下

@Component({
 selector: &#39;movie&#39;,
 styles: [&#39;p {border: 1px solid black}&#39;],
 template: `
<p>
<h3 id="nbsp-title-nbsp">{{ title }}</h3>
<p>
<button (click)="changeStar()">点击切换名字</button>    
<label>Star:</label>
<span>{{star.firstName}} {{star.lastName}}</span>
</p>
</p>`,
changeDetection:ChangeDetectionStrategy.OnPush
})
export class MovieComponent {
 constructor(
  private changeRef:ChangeDetectorRef
 ){}
 @Input() title: string;
 @Input() star;
 
 changeStar(){
  this.star.lastName = &#39;xjl&#39;;
 }
}

此时点击按钮切换名字时,star更改如下

![图片描述][3]

第二种就是上面讲到的使用变化检测对象中的 markForCheck()方法.

ngOnInit() {
  setInterval(() => {
   this.star.lastName = &#39;xjl&#39;;
   this.changeRef.markForCheck();
  }, 1000);
 }

输入属性为Observable

修改app.component.ts

@Component({
 selector: &#39;app-root&#39;,
 template: `
 <h1 id="变更检测策略">变更检测策略</h1>
 <p>{{ slogan }}</p>
 <button type="button" (click)="changeStar()"> 改变明星属性
 </button>
 <button type="button" (click)="changeStarObject()">
   改变明星对象
 </button>
 <movie [title]="title" [star]="star" [addCount]="count"></movie>`,
})
export class AppComponent implements OnInit{
 slogan: string = &#39;change detection&#39;;
 title: string = &#39;OnPush 策略&#39;;
 star: Star = new Star(&#39;周&#39;, &#39;杰伦&#39;);
 count:Observable<any>;

 ngOnInit(){
  this.count = Observable.timer(0, 1000)
 }
 changeStar() {
  this.star.firstName = &#39;吴&#39;;
  this.star.lastName = &#39;彦祖&#39;;
 }
 changeStarObject() {
  this.star = new Star(&#39;刘&#39;, &#39;德华&#39;);
 } 
}

此时,有两种方式让MovieComponent进入检测,一种是使用变化检测对象中的 markForCheck()方法.

ngOnInit() {
  this.addCount.subscribe(() => {
   this.count++;
   this.changeRef.markForCheck();
  })

另外一种是使用async pipe 管道

@Component({
 selector: &#39;movie&#39;,
 styles: [&#39;p {border: 1px solid black}&#39;],
 template: `
<p>
<h3 id="nbsp-title-nbsp">{{ title }}</h3>
<p>
<button (click)="changeStar()">点击切换名字</button>    
<label>Star:</label>
<span>{{star.firstName}} {{star.lastName}}</span>
</p>
<p>{{addCount | async}}</p>
</p>`,
 changeDetection: ChangeDetectionStrategy.OnPush
})

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

利用vue实现如何在表格里,取每行的id(详细教程)

利用vue移动端UI框架如何实现QQ侧边菜单(详细教程)

使用vue及element组件的安装教程(详细教程)

以上是詳細解讀Angular系列中變化檢測問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

JavaScript的演變:當前的趨勢和未來前景JavaScript的演變:當前的趨勢和未來前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

神秘的JavaScript:它的作用以及為什麼重要神秘的JavaScript:它的作用以及為什麼重要Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境