国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

[譯] $digest 在 Angular 中重生

incredible / 1557人閱讀

摘要:但如果一個組件在生命周期鉤子里改變父組件屬性,卻是可以的,因為這個鉤子函數是在更新父組件屬性變化之前調用的注即第步,在第步之前調用。

原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular

我使用 Angular.js 框架好些年了,盡管它飽受批評,但我依然覺得它是個不可思議的框架。我是從這本書 Building your own Angular.js 開始學習的,并且讀了框架的大量源碼,所以我覺得自己對 Angular.js 內部機制比較了解,并且對創建這個框架的架構思想也比較熟悉。最近我在試圖掌握新版 Angular 框架內部架構思想,并與舊版 Angular.js 內部架構思想進行比較。我發現并不是像網上說的那樣,恰恰相反,Angular 大量借鑒了 Angular.js 的設計思想。

其中之一就是名聲糟糕的 digest loop

這個設計的主要問題就是成本太高。改變程序中的任何事物,需要執行成百上千個函數去查詢哪個數據發生變化。而這是 Angular 的基礎部分,但是它會把查詢限定在部分 UI 上,從而提高性能。

如果能更好理解 Angular 是如何實現 digest 的,就可能把你的程序設計的更高效,比如,使用 $scope.$digest() 而不是 $scope.$apply,或者使用不可變對象。但事實是,為了設計出更高效的程序,從而去理解框架內部實現,這可能對很多人來說不是簡單的事情。

所以大量有關 Angular 的文章教程里都宣稱框架里不會再有 $digest cycle 了。這取決于對 digest 概念如何理解,但我認為這很有誤導性,因為它仍然存在。的確,在 Angular 里沒有 scopes 和 watchers,也不再需要調用 $scope.$digest(),但是檢測數據變化的機制依然是遍歷整個組件樹,隱式調用 watchers ,然后更新 DOM。所以實際上是完全重寫了,但被優化增強了,關于新的查詢機制可以查看我寫的 Everything you need to know about change detection in?Angular

digest 的必要性

開始前讓我們先回憶下 Angular.js 中為何存在 digest。所有框架都是在解決數據模型(JavaScript Objects)和 UI(Browser DOM)的同步問題,最大難題是如何知道什么時候數據模型發生改變,而查詢數據模型何時發生改變的過程就是變更檢測(change detection)。這個問題的不同實現方案也是現在眾多前端框架的最大區別點。我計劃寫篇文章,有關不同框架變更檢測實現的比較,如果你感興趣并希望收到通知,可以關注我。

有兩種方式來檢測變化:需要使用者通知框架;通過比較來自動檢測變化。

假設我們有如下一個對象:

let person = {name: "Angular"};

然后我們去更新 name 屬性值,但是框架是怎么知道這個值何時被更新呢?一種方式是需要使用者告訴框架(注:如 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"});

或者強迫用戶去封裝該屬性,從而框架能添加 setters(注:如 Vue 方式):

let app = new Vue({
    data: {
        name: "Hello Vue!"
    }
});
// the setter is triggered so Vue knows what changed
app.name = "Changed";

另一種方式是保存 name 屬性的上一個值,并與當前值進行比較:

if (previousValue !== person.name) // change detected, update DOM

但是什么時候結束比較呢?我們應該在每一次異步代碼運行時都去檢查,由于這部分運行的代碼是作為異步事件去處理,即所謂的 Virtual Machine(VM) turn/tick(注:Virtual Machine 的理解可參考 VM),所以可以緊接著在 VM turn 的后面,執行數據變化檢查代碼。這也是為何 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的話,那我可以說數據變化檢查機制的主要部分在 Angular 里沒有變化,變化的是 digest 的實現。

Angular.js

Angular.js 使用 watcherlistener 的概念,watcher 就是一個返回被監測值的函數,大多數時候這個被監測值就是數據模型的屬性。但也不總是數據模型屬性,如我們可以在作用域里追蹤組件狀態,計算屬性值,第三方組件等等。如果當前返回值與先前值不同,Angular.js 就會調用 listener,而 listener 通常用來更新 UI。

$watch 函數的參數列表如下:

$watch(watcher, listener);

所以,如果我們有一個帶有name 屬性的 person 對象,并在模板里這樣使用 {{name}},那就可以像這樣去追蹤這個屬性變化從而更新 DOM:

$watch(() => {
    return person.name
}, (value) => {
    span.textContent = value
});

這與插值和 ng-bind 類的指令本質上做的一樣,Angular.js 使用指令來映射 DOM 的數據模型。但是 Angular 不再這么去做,它使用屬性映射來連接數據模型和 DOM。上面的示例在 Angular 會這么實現:

由于存在很多組件,并組成了組件樹,每一個組件都有著不同的數據模型,所以就存在分層的 watchers,與分層的組件樹很相似。盡管使用作用域把 watchers 組合在一起,但它們并不相關。

現在,在 digest 期間,Angular.js 會遍歷 watchers 樹并更新 DOM。如果你使用 $timeout$http 或根據需要使用 $scope.$apply$scope.$digest 等方式,就會在每一次異步事件中觸發 digest cycle

watchers 是嚴格按照順序觸發:首先是父組件,然后是子組件。這很有意義,但卻有著不受歡迎的缺點。一個被觸發的 watcher listener 有很多副作用,比如包括更新父組件的屬性。如果父監聽器已經被觸發了,然后子監聽器又去更新父組件屬性,那這個變化不會被檢測到。這就是為何 digest loop 要運行多次來獲取穩定的程序狀態,即確保沒有數據再發生變化。運行次數最大限定為 10 次,這個設計現在被認為是有缺陷的,并且 Angular 不容許這樣做。

Angular

Angular 并沒有類似 Angular.js 中 watcher 概念,但是追蹤模型屬性的函數依然存在。這些函數是由框架編譯器生成的,并且是私有不可訪問的。另外,它們也和 DOM 緊密耦合在一起,這些函數就存儲在生成視圖結構 ViewDefinitionupdateRenderer 中。

它們也很特別:只追蹤模型變化,而不是像 Angular.js 追蹤一切數據變化。每一個組件都有一個 watcher 來追蹤在模板中使用的組件屬性,并對每一個被監聽的屬性調用 checkAndUpdateTextInline 函數。這個函數會比較屬性的上一個值與當前值,如果有變化就更新 DOM。

比如,AppComponent 組件的模板:

Hello {{model.name}}

Angular Compiler 會生成如下類似代碼:

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 一個新項目,執行 ng serve 運行程序后,就可在 Chrome Dev Tools 的 Source Tab 的 ng:// 域下查看到編譯組件后生成的 **.ngfactory.js 文件,即上面類似代碼。

所以,即使 watcher 實現方式不同,但 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 會遍歷 watchers 樹并更新 DOM,這與 Angular 中機制非常類似。在變更檢測循環期間(注:與本文中 digest cycle 相同概念),Angular 也會遍歷組件樹并調用渲染函數更新 DOM。這個過程是 checking and updating view process 過程的一部分,我也寫了一篇長文 Everything you need to know about change detection in Angular

就像 Angular.js 一樣,在 Angular 中變更檢測也同樣是由異步事件觸發(注:如異步請求數據返回事件;用戶點擊按鈕事件;setTimeout/setInterval)。但是由于 Angular 使用 zone 包來給所有異步事件打補丁,所以對于大部分異步事件來說,不需要手動觸發變更檢測。Angular 框架會訂閱 onMicrotaskEmpty 事件,并在一個異步事件完成時會通知 Angular 框架,而這個 onMicrotaskEmpty 事件是在當前 VM Turn 的 microtasks 隊列里不存在任務時被觸發。然而,變更檢測也可以手動方式觸發,如使用 view.detectChangesApplicationRef.tick (注:view.detectChanges 會觸發當前組件及子組件的變更檢測,ApplicationRef.tick 會觸發整個組件樹即所有組件的變更檢測)。

Angular 強調所謂的單向數據流,從頂部流向底部。在父組件完成變更檢測后,低層級里的組件,即子組件,不容許改變父組件的屬性。但如果一個組件在 DoCheck 生命周期鉤子里改變父組件屬性,卻是可以的,因為這個鉤子函數是在更新父組件屬性變化之前調用的(注:即第 6 步 DoCheck, 在 第 9 步 updates DOM interpolations?for the?current view?if properties on?current view?component instance changed 之前調用)。但是,如果改變父組件屬性是在其他階段,比如 AfterViewChecked 鉤子函數階段,在父組件已經完成變更檢測后,再去調用這個鉤子函數,在開發者模式下框架會拋出錯誤:

Expression has changed after it was checked

關于這個錯誤,你可以讀這篇文章 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error 。(注:這篇文章已翻譯)

在生產環境下 Angular 不會拋出錯誤,但是也不會檢查數據變化直到下一次變更檢測循環。(注:因為開發者模式下 Angular 會執行兩次變更檢測循環,第二次檢查會發現父組件屬性被改變就會拋出錯誤,而生產環境下只執行一次。)

使用生命周期鉤子來追蹤數據變化

在 Angular.js 里,每一個組件定義了一堆 watchers 來追蹤如下數據變化:

父組件綁定的屬性

當前組件的屬性

計算屬性值

Angular.js 系統外的第三方組件

在 Angular 里卻是這么實現這些功能的:可以使用 OnChanges 生命周期鉤子函數來監聽父組件屬性;可以使用 DoCheck 生命周期鉤子來監聽當前組件屬性,因為這個鉤子函數會在 Angular 處理當前組件屬性變化前去調用,所以可以在這個函數里做任何需要的事情,來獲取即將在 UI 中顯示的改變值;也可以使用 OnInit 鉤子函數來監聽第三方組件并手動運行變更檢測循環。

比如,我們有一個顯示當前時間的組件,時間是由 Time 服務提供,在 Angular.js 中是這么實現的:

function link(scope, element) {
    scope.$watch(() => {
        return Time.getCurrentTime();
    }, (value) => {
        $scope.time = value;
    })
}

而在 Angular 中是這么實現的:

class TimeComponent {
    ngDoCheck()
    {
        this.time = Time.getCurrentTime();
    }
}

另一個例子是如果我們有一個沒集成在 Angular 系統內的第三方 slider 組件,但我們需要顯示當前 slide,那就僅僅需要把這個組件封裝進 Angular 組件內,監聽 slider"s changed 事件,并手動觸發變更檢測循環來同步 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 里也同樣原理(注:也同樣需要手動觸發變更檢測循環,this.appRef.tick() 會檢測所有組件,而 this.cd.detectChanges() 會檢測當前組件及子組件):

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();
        })
    }
}

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107724.html

相關文章

  • 【Change Detection系列一】$digest Angular新版本重生

    摘要:感謝您的閱讀如果喜歡這篇文章請點贊。它對我意義重大,它能幫助其他人看到這篇文章。對于更高級的文章,你可以在或上跟隨我。 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...

    legendaryedu 評論0 收藏0
  • [] 關于 `ExpressionChangedAfterItHasBeenCheckedErro

    摘要:本文將解釋引起這個錯誤的內在原因,檢測機制的內部原理,提供導致這個錯誤的共同行為,并給出修復這個錯誤的解決方案。這一次過程稱為。這個程序設計為子組件拋出一個事件,而父組件監聽這個事件,而這個事件會引起父組件屬性值發生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...

    andong777 評論0 收藏0
  • [] Controller間的數據共享?最佳實踐:使用Service

    摘要:共享數據的最佳策略是什么呢用一些變態的控制器繼承方案嗎當然不是,最簡單容易的方式就是使用服務。概括創建一個服務去存放你的數據,并給數據創建和的方法。 原文鏈接 : Sharing Data Between Controllers? Best Practice: Use a Service原文作者 : DAVE CEDDIA譯者 : 李林璞(web前端領域)譯者注:翻譯如有疏漏,歡迎指出...

    Salamander 評論0 收藏0
  • angular的ViewModel設計

    摘要:換言之,的對應的,此外它還有。它們共同構成的監控系統。和是相輔相成的。兩者一起,構成了作用域的核心功能數據變化的響應。迭代的最大值稱為??蚣茉O計第三版,敬請期待 angular的ViewModel有一個專門的官方術語叫$scope, 它只是一個普通構造器(Scope)的實例。換言之,它是一個普通的JS對象。為了實現MVVM框架通常宣傳的那種改變數據即改變視圖的魔幻效果,它得裝備上更多更...

    int64 評論0 收藏0
  • angular性能優化心得

    摘要:本文針對的讀者具備性能優化的相關知識雅虎條性能優化原則高性能網站建設指南等擁有實戰經驗。這種機制能減少瀏覽器次數,從而提高性能。僅會檢查該和它的子,當你確定當前操作僅影響它們時,用可以稍微提升性能。 搬運自: http://atian25.github.io/2014/05/09/angular-performace/ 不知不覺,在項目中用angular已經半年多了,踩了很多坑...

    guqiu 評論0 收藏0

發表評論

0條評論

incredible

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<