摘要:但如果一個(gè)組件在生命周期鉤子里改變父組件屬性,卻是可以的,因?yàn)檫@個(gè)鉤子函數(shù)是在更新父組件屬性變化之前調(diào)用的注即第步,在第步之前調(diào)用。
原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular
我使用 Angular.js 框架好些年了,盡管它飽受批評(píng),但我依然覺得它是個(gè)不可思議的框架。我是從這本書 Building your own Angular.js 開始學(xué)習(xí)的,并且讀了框架的大量源碼,所以我覺得自己對(duì) Angular.js 內(nèi)部機(jī)制比較了解,并且對(duì)創(chuàng)建這個(gè)框架的架構(gòu)思想也比較熟悉。最近我在試圖掌握新版 Angular 框架內(nèi)部架構(gòu)思想,并與舊版 Angular.js 內(nèi)部架構(gòu)思想進(jìn)行比較。我發(fā)現(xiàn)并不是像網(wǎng)上說的那樣,恰恰相反,Angular 大量借鑒了 Angular.js 的設(shè)計(jì)思想。
其中之一就是名聲糟糕的 digest loop:
這個(gè)設(shè)計(jì)的主要問題就是成本太高。改變程序中的任何事物,需要執(zhí)行成百上千個(gè)函數(shù)去查詢哪個(gè)數(shù)據(jù)發(fā)生變化。而這是 Angular 的基礎(chǔ)部分,但是它會(huì)把查詢限定在部分 UI 上,從而提高性能。
如果能更好理解 Angular 是如何實(shí)現(xiàn) digest 的,就可能把你的程序設(shè)計(jì)的更高效,比如,使用 $scope.$digest() 而不是 $scope.$apply,或者使用不可變對(duì)象。但事實(shí)是,為了設(shè)計(jì)出更高效的程序,從而去理解框架內(nèi)部實(shí)現(xiàn),這可能對(duì)很多人來說不是簡(jiǎn)單的事情。
所以大量有關(guān) Angular 的文章教程里都宣稱框架里不會(huì)再有 $digest cycle 了。這取決于對(duì) digest 概念如何理解,但我認(rèn)為這很有誤導(dǎo)性,因?yàn)樗匀淮嬖?。的確,在 Angular 里沒有 scopes 和 watchers,也不再需要調(diào)用 $scope.$digest(),但是檢測(cè)數(shù)據(jù)變化的機(jī)制依然是遍歷整個(gè)組件樹,隱式調(diào)用 watchers ,然后更新 DOM。所以實(shí)際上是完全重寫了,但被優(yōu)化增強(qiáng)了,關(guān)于新的查詢機(jī)制可以查看我寫的 Everything you need to know about change detection in?Angular。
digest 的必要性開始前讓我們先回憶下 Angular.js 中為何存在 digest。所有框架都是在解決數(shù)據(jù)模型(JavaScript Objects)和 UI(Browser DOM)的同步問題,最大難題是如何知道什么時(shí)候數(shù)據(jù)模型發(fā)生改變,而查詢數(shù)據(jù)模型何時(shí)發(fā)生改變的過程就是變更檢測(cè)(change detection)。這個(gè)問題的不同實(shí)現(xiàn)方案也是現(xiàn)在眾多前端框架的最大區(qū)別點(diǎn)。我計(jì)劃寫篇文章,有關(guān)不同框架變更檢測(cè)實(shí)現(xiàn)的比較,如果你感興趣并希望收到通知,可以關(guān)注我。
有兩種方式來檢測(cè)變化:需要使用者通知框架;通過比較來自動(dòng)檢測(cè)變化。
假設(shè)我們有如下一個(gè)對(duì)象:
let person = {name: "Angular"};
然后我們?nèi)ジ?name 屬性值,但是框架是怎么知道這個(gè)值何時(shí)被更新呢?一種方式是需要使用者告訴框架(注:如 React 方式):
constructor() { let person = {name: "Angular"}; this.state = person; } ... // explicitly notifying React about the changes // and specifying what is about to change this.setState({name: "Changed"});
或者強(qiáng)迫用戶去封裝該屬性,從而框架能添加 setters(注:如 Vue 方式):
let app = new Vue({ data: { name: "Hello Vue!" } }); // the setter is triggered so Vue knows what changed app.name = "Changed";
另一種方式是保存 name 屬性的上一個(gè)值,并與當(dāng)前值進(jìn)行比較:
if (previousValue !== person.name) // change detected, update DOM
但是什么時(shí)候結(jié)束比較呢?我們應(yīng)該在每一次異步代碼運(yùn)行時(shí)都去檢查,由于這部分運(yùn)行的代碼是作為異步事件去處理,即所謂的 Virtual Machine(VM) turn/tick(注:Virtual Machine 的理解可參考 VM),所以可以緊接著在 VM turn 的后面,執(zhí)行數(shù)據(jù)變化檢查代碼。這也是為何 Angular.js 使用 digest,所以我們可以定義 digest 為(注:為清晰理解,不翻譯):
change detection mechanism that walks the tree of components,?checks each component for changes?and?updates DOM when a component property is?changed。
如果我們這么去定義 digest的話,那我可以說數(shù)據(jù)變化檢查機(jī)制的主要部分在 Angular 里沒有變化,變化的是 digest 的實(shí)現(xiàn)。
Angular.jsAngular.js 使用 watcher 和 listener 的概念,watcher 就是一個(gè)返回被監(jiān)測(cè)值的函數(shù),大多數(shù)時(shí)候這個(gè)被監(jiān)測(cè)值就是數(shù)據(jù)模型的屬性。但也不總是數(shù)據(jù)模型屬性,如我們可以在作用域里追蹤組件狀態(tài),計(jì)算屬性值,第三方組件等等。如果當(dāng)前返回值與先前值不同,Angular.js 就會(huì)調(diào)用 listener,而 listener 通常用來更新 UI。
$watch 函數(shù)的參數(shù)列表如下:
$watch(watcher, listener);
所以,如果我們有一個(gè)帶有name 屬性的 person 對(duì)象,并在模板里這樣使用 {{name}},那就可以像這樣去追蹤這個(gè)屬性變化從而更新 DOM:
$watch(() => { return person.name }, (value) => { span.textContent = value });
這與插值和 ng-bind 類的指令本質(zhì)上做的一樣,Angular.js 使用指令來映射 DOM 的數(shù)據(jù)模型。但是 Angular 不再這么去做,它使用屬性映射來連接數(shù)據(jù)模型和 DOM。上面的示例在 Angular 會(huì)這么實(shí)現(xiàn):
由于存在很多組件,并組成了組件樹,每一個(gè)組件都有著不同的數(shù)據(jù)模型,所以就存在分層的 watchers,與分層的組件樹很相似。盡管使用作用域把 watchers 組合在一起,但它們并不相關(guān)。
現(xiàn)在,在 digest 期間,Angular.js 會(huì)遍歷 watchers 樹并更新 DOM。如果你使用 $timeout,$http 或根據(jù)需要使用 $scope.$apply 和 $scope.$digest 等方式,就會(huì)在每一次異步事件中觸發(fā) digest cycle。
watchers 是嚴(yán)格按照順序觸發(fā):首先是父組件,然后是子組件。這很有意義,但卻有著不受歡迎的缺點(diǎn)。一個(gè)被觸發(fā)的 watcher listener 有很多副作用,比如包括更新父組件的屬性。如果父監(jiān)聽器已經(jīng)被觸發(fā)了,然后子監(jiān)聽器又去更新父組件屬性,那這個(gè)變化不會(huì)被檢測(cè)到。這就是為何 digest loop 要運(yùn)行多次來獲取穩(wěn)定的程序狀態(tài),即確保沒有數(shù)據(jù)再發(fā)生變化。運(yùn)行次數(shù)最大限定為 10 次,這個(gè)設(shè)計(jì)現(xiàn)在被認(rèn)為是有缺陷的,并且 Angular 不容許這樣做。
AngularAngular 并沒有類似 Angular.js 中 watcher 概念,但是追蹤模型屬性的函數(shù)依然存在。這些函數(shù)是由框架編譯器生成的,并且是私有不可訪問的。另外,它們也和 DOM 緊密耦合在一起,這些函數(shù)就存儲(chǔ)在生成視圖結(jié)構(gòu) ViewDefinition 的 updateRenderer 中。
它們也很特別:只追蹤模型變化,而不是像 Angular.js 追蹤一切數(shù)據(jù)變化。每一個(gè)組件都有一個(gè) watcher 來追蹤在模板中使用的組件屬性,并對(duì)每一個(gè)被監(jiān)聽的屬性調(diào)用 checkAndUpdateTextInline 函數(shù)。這個(gè)函數(shù)會(huì)比較屬性的上一個(gè)值與當(dāng)前值,如果有變化就更新 DOM。
比如,AppComponent 組件的模板:
Hello {{model.name}}
Angular Compiler 會(huì)生成如下類似代碼:
function View_AppComponent_0(l) { // jit_viewDef2 is `viewDef` constructor return jit_viewDef2(0, // array of nodes generated from the template // first node for `h1` element // second node is textNode for `Hello {{model.name}}` [ jit_elementDef3(...), jit_textDef4(...) ], ... // updateRenderer function similar to a watcher function (ck, v) { var co = v.component; // gets current value for the component `name` property var currVal_0 = co.model.name; // calls CheckAndUpdateNode function passing // currentView and node index (1) which uses // interpolated `currVal_0` value ck(v, 1, 0, currVal_0); }); }
注:使用 Angular-CLI ng new 一個(gè)新項(xiàng)目,執(zhí)行 ng serve 運(yùn)行程序后,就可在 Chrome Dev Tools 的 Source Tab 的 ng:// 域下查看到編譯組件后生成的 **.ngfactory.js 文件,即上面類似代碼。
所以,即使 watcher 實(shí)現(xiàn)方式不同,但 digest loop 仍然存在,僅僅是換了名字為 change detection cycle (注: 為清晰理解,不翻譯):
In development mode,?tick()?also performs a second?change detection cycle?to ensure that no further changes are detected.
上文說到在 digest 期間,Angular.js 會(huì)遍歷 watchers 樹并更新 DOM,這與 Angular 中機(jī)制非常類似。在變更檢測(cè)循環(huán)期間(注:與本文中 digest cycle 相同概念),Angular 也會(huì)遍歷組件樹并調(diào)用渲染函數(shù)更新 DOM。這個(gè)過程是 checking and updating view process 過程的一部分,我也寫了一篇長(zhǎng)文 Everything you need to know about change detection in Angular 。
就像 Angular.js 一樣,在 Angular 中變更檢測(cè)也同樣是由異步事件觸發(fā)(注:如異步請(qǐng)求數(shù)據(jù)返回事件;用戶點(diǎn)擊按鈕事件;setTimeout/setInterval)。但是由于 Angular 使用 zone 包來給所有異步事件打補(bǔ)丁,所以對(duì)于大部分異步事件來說,不需要手動(dòng)觸發(fā)變更檢測(cè)。Angular 框架會(huì)訂閱 onMicrotaskEmpty 事件,并在一個(gè)異步事件完成時(shí)會(huì)通知 Angular 框架,而這個(gè) onMicrotaskEmpty 事件是在當(dāng)前 VM Turn 的 microtasks 隊(duì)列里不存在任務(wù)時(shí)被觸發(fā)。然而,變更檢測(cè)也可以手動(dòng)方式觸發(fā),如使用 view.detectChanges 或 ApplicationRef.tick (注:view.detectChanges 會(huì)觸發(fā)當(dāng)前組件及子組件的變更檢測(cè),ApplicationRef.tick 會(huì)觸發(fā)整個(gè)組件樹即所有組件的變更檢測(cè))。
Angular 強(qiáng)調(diào)所謂的單向數(shù)據(jù)流,從頂部流向底部。在父組件完成變更檢測(cè)后,低層級(jí)里的組件,即子組件,不容許改變父組件的屬性。但如果一個(gè)組件在 DoCheck 生命周期鉤子里改變父組件屬性,卻是可以的,因?yàn)檫@個(gè)鉤子函數(shù)是在更新父組件屬性變化之前調(diào)用的(注:即第 6 步 DoCheck, 在 第 9 步 updates DOM interpolations?for the?current view?if properties on?current view?component instance changed 之前調(diào)用)。但是,如果改變父組件屬性是在其他階段,比如 AfterViewChecked 鉤子函數(shù)階段,在父組件已經(jīng)完成變更檢測(cè)后,再去調(diào)用這個(gè)鉤子函數(shù),在開發(fā)者模式下框架會(huì)拋出錯(cuò)誤:
Expression has changed after it was checked
關(guān)于這個(gè)錯(cuò)誤,你可以讀這篇文章 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error 。(注:這篇文章已翻譯)
在生產(chǎn)環(huán)境下 Angular 不會(huì)拋出錯(cuò)誤,但是也不會(huì)檢查數(shù)據(jù)變化直到下一次變更檢測(cè)循環(huán)。(注:因?yàn)殚_發(fā)者模式下 Angular 會(huì)執(zhí)行兩次變更檢測(cè)循環(huán),第二次檢查會(huì)發(fā)現(xiàn)父組件屬性被改變就會(huì)拋出錯(cuò)誤,而生產(chǎn)環(huán)境下只執(zhí)行一次。)
使用生命周期鉤子來追蹤數(shù)據(jù)變化在 Angular.js 里,每一個(gè)組件定義了一堆 watchers 來追蹤如下數(shù)據(jù)變化:
父組件綁定的屬性
當(dāng)前組件的屬性
計(jì)算屬性值
Angular.js 系統(tǒng)外的第三方組件
在 Angular 里卻是這么實(shí)現(xiàn)這些功能的:可以使用 OnChanges 生命周期鉤子函數(shù)來監(jiān)聽父組件屬性;可以使用 DoCheck 生命周期鉤子來監(jiān)聽當(dāng)前組件屬性,因?yàn)檫@個(gè)鉤子函數(shù)會(huì)在 Angular 處理當(dāng)前組件屬性變化前去調(diào)用,所以可以在這個(gè)函數(shù)里做任何需要的事情,來獲取即將在 UI 中顯示的改變值;也可以使用 OnInit 鉤子函數(shù)來監(jiān)聽第三方組件并手動(dòng)運(yùn)行變更檢測(cè)循環(huán)。
比如,我們有一個(gè)顯示當(dāng)前時(shí)間的組件,時(shí)間是由 Time 服務(wù)提供,在 Angular.js 中是這么實(shí)現(xiàn)的:
function link(scope, element) { scope.$watch(() => { return Time.getCurrentTime(); }, (value) => { $scope.time = value; }) }
而在 Angular 中是這么實(shí)現(xiàn)的:
class TimeComponent { ngDoCheck() { this.time = Time.getCurrentTime(); } }
另一個(gè)例子是如果我們有一個(gè)沒集成在 Angular 系統(tǒng)內(nèi)的第三方 slider 組件,但我們需要顯示當(dāng)前 slide,那就僅僅需要把這個(gè)組件封裝進(jìn) Angular 組件內(nèi),監(jiān)聽 slider"s changed 事件,并手動(dòng)觸發(fā)變更檢測(cè)循環(huán)來同步 UI。Angular.js 里這么寫:
function link(scope, element) { slider.on("changed", (slide) => { scope.slide = slide; // detect changes on the current component $scope.$digest(); // or run change detection for the all app $rootScope.$digest(); }) }
Angular 里也同樣原理(注:也同樣需要手動(dòng)觸發(fā)變更檢測(cè)循環(huán),this.appRef.tick() 會(huì)檢測(cè)所有組件,而 this.cd.detectChanges() 會(huì)檢測(cè)當(dāng)前組件及子組件):
class SliderComponent { ngOnInit() { slider.on("changed", (slide) => { this.slide = slide // detect changes on the current component // this.cd is an injected ChangeDetector instance this.cd.detectChanges(); // or run change detection for the all app // this.appRef is an ApplicationRef instance this.appRef.tick(); }) } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/107724.html
摘要:感謝您的閱讀如果喜歡這篇文章請(qǐng)點(diǎn)贊。它對(duì)我意義重大,它能幫助其他人看到這篇文章。對(duì)于更高級(jí)的文章,你可以在或上跟隨我。 I’ve worked with Angular.js for a few years and despite the widespread criticism I think this is a fantastic framework. I’ve started w...
摘要:本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測(cè)機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。這一次過程稱為。這個(gè)程序設(shè)計(jì)為子組件拋出一個(gè)事件,而父組件監(jiān)聽這個(gè)事件,而這個(gè)事件會(huì)引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...
摘要:共享數(shù)據(jù)的最佳策略是什么呢用一些變態(tài)的控制器繼承方案嗎當(dāng)然不是,最簡(jiǎn)單容易的方式就是使用服務(wù)。概括創(chuàng)建一個(gè)服務(wù)去存放你的數(shù)據(jù),并給數(shù)據(jù)創(chuàng)建和的方法。 原文鏈接 : Sharing Data Between Controllers? Best Practice: Use a Service原文作者 : DAVE CEDDIA譯者 : 李林璞(web前端領(lǐng)域)譯者注:翻譯如有疏漏,歡迎指出...
摘要:換言之,的對(duì)應(yīng)的,此外它還有。它們共同構(gòu)成的監(jiān)控系統(tǒng)。和是相輔相成的。兩者一起,構(gòu)成了作用域的核心功能數(shù)據(jù)變化的響應(yīng)。迭代的最大值稱為??蚣茉O(shè)計(jì)第三版,敬請(qǐng)期待 angular的ViewModel有一個(gè)專門的官方術(shù)語叫$scope, 它只是一個(gè)普通構(gòu)造器(Scope)的實(shí)例。換言之,它是一個(gè)普通的JS對(duì)象。為了實(shí)現(xiàn)MVVM框架通常宣傳的那種改變數(shù)據(jù)即改變視圖的魔幻效果,它得裝備上更多更...
摘要:本文針對(duì)的讀者具備性能優(yōu)化的相關(guān)知識(shí)雅虎條性能優(yōu)化原則高性能網(wǎng)站建設(shè)指南等擁有實(shí)戰(zhàn)經(jīng)驗(yàn)。這種機(jī)制能減少瀏覽器次數(shù),從而提高性能。僅會(huì)檢查該和它的子,當(dāng)你確定當(dāng)前操作僅影響它們時(shí),用可以稍微提升性能。 搬運(yùn)自: http://atian25.github.io/2014/05/09/angular-performace/ 不知不覺,在項(xiàng)目中用angular已經(jīng)半年多了,踩了很多坑...
閱讀 1780·2023-04-26 01:41
閱讀 3081·2021-11-23 09:51
閱讀 2744·2021-10-09 09:43
閱讀 9054·2021-09-22 15:13
閱讀 2460·2021-09-07 09:59
閱讀 2632·2019-08-30 15:44
閱讀 1138·2019-08-30 12:45
閱讀 2624·2019-08-30 12:43