成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

[譯] Angular DOM 更新機(jī)制

xumenger / 3315人閱讀

摘要:注更新元素節(jié)點(diǎn)和文本節(jié)點(diǎn)都提到了渲染器,這也是一個(gè)重要的概念。每一個(gè)視圖對(duì)象都有一個(gè)屬性,即是的引用,也就是組件渲染器,的實(shí)際更新操作由它完成。

原文鏈接:The mechanics of DOM updates in Angular

由模型變化觸發(fā)的 DOM 更新是所有前端框架的重要功能(注:即保持 model 和 view 的同步),當(dāng)然 Angular 也不例外。定義一個(gè)如下模板表達(dá)式:

Hello {{name}}

或者類(lèi)似下面的屬性綁定(注:這與上面代碼等價(jià)):

當(dāng)每次 name 值發(fā)生變化時(shí),Angular 會(huì)神奇般的自動(dòng)更新 DOM 元素(注:最上面代碼是更新 DOM 文本節(jié)點(diǎn),上面代碼是更新 DOM 元素節(jié)點(diǎn),兩者是不一樣的,下文解釋?zhuān)?。這表面上看起來(lái)很簡(jiǎn)單,但是其內(nèi)部工作相當(dāng)復(fù)雜。而且,DOM 更新僅僅是 Angular 變更檢測(cè)機(jī)制 的一部分,變更檢測(cè)機(jī)制主要由以下三步組成:

DOM updates(注:即本文將要解釋的內(nèi)容)

child components Input bindings updates

query list updates

本文主要探索變更檢測(cè)機(jī)制的渲染部分(即 DOM updates 部分)。如果你之前也對(duì)這個(gè)問(wèn)題很好奇,可以繼續(xù)讀下去,絕對(duì)讓你茅塞頓開(kāi)。

在引用相關(guān)源碼時(shí),假設(shè)程序是以生產(chǎn)模式運(yùn)行。讓我們開(kāi)始吧!

程序內(nèi)部架構(gòu)

在探索 DOM 更新之前,我們先搞清楚 Angular 程序內(nèi)部究竟是如何設(shè)計(jì)的,簡(jiǎn)單回顧下吧。

視圖

從我的這篇文章 Here is what you need to know about dynamic components in?Angular 知道 Angular 編譯器會(huì)把程序中使用的組件編譯為一個(gè)工廠類(lèi)(factory)。例如,下面代碼展示 Angular 如何從工廠類(lèi)中創(chuàng)建一個(gè)組件(注:這里作者邏輯貌似有點(diǎn)亂,前一句說(shuō)的 Angular 編譯器編譯的工廠類(lèi),其實(shí)是編譯器去做的,不需要開(kāi)發(fā)者做任何事情,是自動(dòng)化的事情;而下面代碼說(shuō)的是開(kāi)發(fā)者如何手動(dòng)通過(guò) ComponentFactory 來(lái)創(chuàng)建一個(gè) Component 實(shí)例??傊?,他是想說(shuō)組件是怎么被實(shí)例化的):

const factory = r.resolveComponentFactory(AComponent);
componentRef: ComponentRef = factory.create(injector);

Angular 使用這個(gè)工廠類(lèi)來(lái)實(shí)例化 View Definition ,然后使用 viewDef 函數(shù)來(lái) 創(chuàng)建視圖。Angular 內(nèi)部把一個(gè)程序看作為一顆視圖樹(shù),一個(gè)程序雖然有眾多組件,但有一個(gè)公共的視圖定義接口來(lái)定義由組件生成的視圖結(jié)構(gòu)(注:即 ViewDefinition Interface),當(dāng)然 Angular 使用每一個(gè)組件對(duì)象來(lái)創(chuàng)建對(duì)應(yīng)的視圖,從而由多個(gè)視圖組成視圖樹(shù)。(注:這里有一個(gè)主要概念就是視圖,其結(jié)構(gòu)就是 ViewDefinition Interface

組件工廠

組件工廠大部分代碼是由編譯器生成的不同視圖節(jié)點(diǎn)組成的,這些視圖節(jié)點(diǎn)是通過(guò)模板解析生成的(注:編譯器生成的組件工廠是一個(gè)返回值為函數(shù)的函數(shù),上文的 ComponentFactory 是 Angular 提供的類(lèi),供手動(dòng)調(diào)用。當(dāng)然,兩者指向同一個(gè)事物,只是表現(xiàn)形式不同而已)。假設(shè)定義一個(gè)組件的模板如下:

I am {{name}}

編譯器會(huì)解析這個(gè)模板生成包含如下類(lèi)似的組件工廠代碼(注:這只是最重要的部分代碼):

function View_AComponent_0(l) {
    return jit_viewDef1(0,
        [
          jit_elementDef2(0,null,null,1,"span",...),
          jit_textDef3(null,["I am ",...])
        ], 
        null,
        function(_ck,_v) {
            var _co = _v.component;
            var currVal_0 = _co.name;
            _ck(_v,1,0,currVal_0);
注:由 AppComponent 組件編譯生成的工廠函數(shù)完整代碼如下
 (function(jit_createRendererType2_0,jit_viewDef_1,jit_elementDef_2,jit_textDef_3) {
     var styles_AppComponent = [""];
     var RenderType_AppComponent = jit_createRendererType2_0({encapsulation:0,styles:styles_AppComponent,data:{}});
     function View_AppComponent_0(_l) {
         return jit_viewDef_1(0,
            [
                (_l()(),jit_elementDef_2(0,0,null,null,1,"span",[],null,null,null,null,null)),
                (_l()(),jit_textDef_3(1,null,["I am ",""]))
            ],
            null,
            function(_ck,_v) {
                var _co = _v.component;
                var currVal_0 = _co.name;
                _ck(_v,1,0,currVal_0);
           });
    }
 return {RenderType_AppComponent:RenderType_AppComponent,View_AppComponent_0:View_AppComponent_0};})

上面代碼描述了視圖的結(jié)構(gòu),并在實(shí)例化組件時(shí)會(huì)被調(diào)用。jit_viewDef_1 其實(shí)就是 viewDef 函數(shù),用來(lái)創(chuàng)建視圖(注:viewDef 函數(shù)很重要,因?yàn)橐晥D是調(diào)用它創(chuàng)建的,生成的視圖結(jié)構(gòu)即是 ViewDefinition)。

viewDef 函數(shù)的第二個(gè)參數(shù) nodes 有些類(lèi)似 html 中節(jié)點(diǎn)的意思,但卻不僅僅如此。上面代碼中第二個(gè)參數(shù)是一個(gè)數(shù)組,其第一個(gè)數(shù)組元素 jit_elementDef_2 是元素節(jié)點(diǎn)定義,第二個(gè)數(shù)組元素 jit_textDef_3 是文本節(jié)點(diǎn)定義。Angular 編譯器會(huì)生成很多不同的節(jié)點(diǎn)定義,節(jié)點(diǎn)類(lèi)型是由 NodeFlags 設(shè)置的。稍后我們將看到 Angular 如何根據(jù)不同節(jié)點(diǎn)類(lèi)型來(lái)做 DOM 更新。

本文只對(duì)元素和文本節(jié)點(diǎn)感興趣:

export const enum NodeFlags {
    TypeElement = 1 << 0, 
    TypeText = 1 << 1

讓我們簡(jiǎn)要擼一遍。

注:上文作者說(shuō)了一大段,其實(shí)核心就是,程序是一堆視圖組成的,而每一個(gè)視圖又是由不同類(lèi)型節(jié)點(diǎn)組成的。而本文只關(guān)心元素節(jié)點(diǎn)和文本節(jié)點(diǎn),至于還有個(gè)重要的指令節(jié)點(diǎn)在另一篇文章。
元素節(jié)點(diǎn)的結(jié)構(gòu)定義

元素節(jié)點(diǎn)結(jié)構(gòu) 是 Angular 編譯每一個(gè) html 元素生成的節(jié)點(diǎn)結(jié)構(gòu),它也是用來(lái)生成組件的,如對(duì)這點(diǎn)感興趣可查看 Here is why you will not find components inside?Angular。元素節(jié)點(diǎn)也可以包含其他元素節(jié)點(diǎn)和文本節(jié)點(diǎn)作為子節(jié)點(diǎn),子節(jié)點(diǎn)數(shù)量是由 childCount 設(shè)置的。

所有元素定義是由 elementRef 函數(shù)生成的,而工廠函數(shù)中的 jit_elementDef_2() 就是這個(gè)函數(shù)。elementRef() 主要有以下幾個(gè)一般性參數(shù):

Name Description
childCount specifies how many children the current element have
namespaceAndName the name of the html element(注:如 "span")
fixedAttrs attributes defined on the element

還有其他的幾個(gè)具有特定性能的參數(shù):

Name Description
matchedQueriesDsl used when querying child nodes
ngContentIndex used for node projection
bindings used for dom and bound properties update
outputs, handleEvent used for event propagation

本文主要對(duì) bindings 感興趣。

注:從上文知道視圖(view)是由不同類(lèi)型節(jié)點(diǎn)(nodes)組成的,而元素節(jié)點(diǎn)(element nodes)是由 elementRef 函數(shù)生成的,元素節(jié)點(diǎn)的結(jié)構(gòu)是由 ElementDef 定義的。
文本節(jié)點(diǎn)的結(jié)構(gòu)定義

文本節(jié)點(diǎn)結(jié)構(gòu) 是 Angular 編譯每一個(gè) html 文本 生成的節(jié)點(diǎn)結(jié)構(gòu)。通常它是元素定義節(jié)點(diǎn)的子節(jié)點(diǎn),就像我們本文的示例那樣(注:I am {{name}},span 是元素節(jié)點(diǎn),I am {{name}} 是文本節(jié)點(diǎn),也是 span 的子節(jié)點(diǎn))。這個(gè)文本節(jié)點(diǎn)是由 textDef 函數(shù)生成的。它的第二個(gè)參數(shù)以字符串?dāng)?shù)組形式傳進(jìn)來(lái)(注: Angular v5.* 是第三個(gè)參數(shù))。例如,下面的文本:

Hello {{name}} and another {{prop}}

將要被解析為一個(gè)數(shù)組:

["Hello ", " and another ", ""]

然后被用來(lái)生成正確的綁定:

{
  text: "Hello",
  bindings: [
    {
      name: "name",
      suffix: " and another "
    },
    {
      name: "prop",
      suffix: ""
    }
  ]
}

在臟檢查(注:即變更檢測(cè))階段會(huì)這么用來(lái)生成文本:

text
+ context[bindings[0][property]] + context[bindings[0][suffix]]
+ context[bindings[1][property]] + context[bindings[1][suffix]]
注:同上,文本節(jié)點(diǎn)是由 textDef 函數(shù)生成的,結(jié)構(gòu)是由 TextDef 定義的。既然已經(jīng)知道了兩個(gè)節(jié)點(diǎn)的定義和生成,那節(jié)點(diǎn)上的屬性綁定, Angular 是怎么處理的呢?
節(jié)點(diǎn)的綁定

Angular 使用 BindingDef 來(lái)定義每一個(gè)節(jié)點(diǎn)的綁定依賴(lài),而這些綁定依賴(lài)通常是組件類(lèi)的屬性。在變更檢測(cè)時(shí) Angular 會(huì)根據(jù)這些綁定來(lái)決定如何更新節(jié)點(diǎn)和提供上下文信息。具體哪一種操作是由 BindingFlags 決定的,下面列表展示了具體的 DOM 操作類(lèi)型:

Name Construction in template
TypeElementAttribute attr.name
TypeElementClass class.name
TypeElementStyle style.name

元素和文本定義根據(jù)這些編譯器可識(shí)別的綁定標(biāo)志位,內(nèi)部創(chuàng)建這些綁定依賴(lài)。每一種節(jié)點(diǎn)類(lèi)型都有著不同的綁定生成邏輯(注:意思是 Angular 會(huì)根據(jù) BindingFlags 來(lái)生成對(duì)應(yīng)的 BindingDef)。

更新渲染器

最讓我們感興趣的是 jit_viewDef_1 中最后那個(gè)參數(shù):

function(_ck,_v) {
   var _co = _v.component;
   var currVal_0 = _co.name;
   _ck(_v,1,0,currVal_0);
});

這個(gè)函數(shù)叫做 updateRenderer。它接收兩個(gè)參數(shù):_ck_v。_ckcheck 的簡(jiǎn)寫(xiě),其實(shí)就是 prodCheckAndUpdateNode 函數(shù),而 _v 就是當(dāng)前視圖對(duì)象。updateRenderer 函數(shù)會(huì)在 每一次變更檢測(cè)時(shí) 被調(diào)用,其參數(shù) _ck_v 也是這時(shí)被傳入。

updateRenderer 函數(shù)邏輯主要是,從組件對(duì)象的綁定屬性獲取當(dāng)前值,并調(diào)用 _ck 函數(shù),同時(shí)傳入視圖對(duì)象、視圖節(jié)點(diǎn)索引和綁定屬性當(dāng)前值。重要一點(diǎn)是 Angular 會(huì)為每一個(gè)視圖執(zhí)行 DOM 更新操作,所以必須傳入視圖節(jié)點(diǎn)索引參數(shù)(注:這個(gè)很好理解,上文說(shuō)了 Angular 會(huì)依次對(duì)每一個(gè) view 做模型視圖同步過(guò)程)。你可以清晰看到 _ck 參數(shù)列表:

function prodCheckAndUpdateNode(
    view: ViewData, 
    nodeIndex: number, 
    argStyle: ArgumentType, 
    v0?: any, 
    v1?: any, 
    v2?: any,

nodeIndex 是視圖節(jié)點(diǎn)的索引,如果你模板中有多個(gè)表達(dá)式:

Hello {{name}}

Hello {{age}}

編譯器生成的 updateRenderer 函數(shù)如下:

var _co = _v.component;

// here node index is 1 and property is `name`
var currVal_0 = _co.name;
_ck(_v,1,0,currVal_0);

// here node index is 4 and bound property is `age`
var currVal_1 = _co.age;
_ck(_v,4,0,currVal_1);
更新 DOM

現(xiàn)在我們已經(jīng)知道 Angular 編譯器生成的所有對(duì)象(注:已經(jīng)有了 view,element node,text node 和 updateRenderer 這幾個(gè)道具),現(xiàn)在我們可以探索如何使用這些對(duì)象來(lái)更新 DOM。

從上文我們知道變更檢測(cè)期間 updateRenderer 函數(shù)傳入的一個(gè)參數(shù)是 _ck 函數(shù),而這個(gè)函數(shù)就是 prodCheckAndUpdateNode。這個(gè)函數(shù)在繼續(xù)執(zhí)行后,最終會(huì)調(diào)用 checkAndUpdateNodeInline ,如果綁定屬性的數(shù)量超過(guò) 10,Angular 還提供了 checkAndUpdateNodeDynamic 這個(gè)函數(shù)(注:兩個(gè)函數(shù)本質(zhì)一樣)。

checkAndUpdateNodeInline 函數(shù)會(huì)根據(jù)不同視圖節(jié)點(diǎn)類(lèi)型來(lái)執(zhí)行對(duì)應(yīng)的檢查更新函數(shù):

case NodeFlags.TypeElement   -> checkAndUpdateElementInline
case NodeFlags.TypeText      -> checkAndUpdateTextInline
case NodeFlags.TypeDirective -> checkAndUpdateDirectiveInline

讓我們看下這些函數(shù)是做什么的,至于 NodeFlags.TypeDirective 可以查看我寫(xiě)的文章 The mechanics of property bindings update in Angular 。

注:因?yàn)楸疚闹魂P(guān)注 element node 和 text node
元素節(jié)點(diǎn)

對(duì)于元素節(jié)點(diǎn),會(huì)調(diào)用函數(shù) checkAndUpdateElementInline 以及 checkAndUpdateElementValuecheckAndUpdateElementValue 函數(shù)會(huì)檢查綁定形式是否是 [attr.name, class.name, style.some] 或是屬性綁定形式:

case BindingFlags.TypeElementAttribute -> setElementAttribute
case BindingFlags.TypeElementClass     -> setElementClass
case BindingFlags.TypeElementStyle     -> setElementStyle
case BindingFlags.TypeProperty         -> setElementProperty;

然后使用渲染器對(duì)應(yīng)的方法來(lái)對(duì)該節(jié)點(diǎn)執(zhí)行對(duì)應(yīng)操作,比如使用 setElementClass 給當(dāng)前節(jié)點(diǎn) span 添加一個(gè) class

文本節(jié)點(diǎn)

對(duì)于文本節(jié)點(diǎn)類(lèi)型,會(huì)調(diào)用 checkAndUpdateTextInline ,下面是主要部分:

if (checkAndUpdateBinding(view, nodeDef, bindingIndex, newValue)) {
    value = text + _addInterpolationPart(...);
    view.renderer.setValue(DOMNode, value);
}

它會(huì)拿到 updateRenderer 函數(shù)傳過(guò)來(lái)的當(dāng)前值(注:即上文的 _ck(_v,4,0,currVal_1);),與上一次變更檢測(cè)時(shí)的值相比較。視圖數(shù)據(jù)包含有 oldValues 屬性,如果屬性值如 name 發(fā)生變化,Angular 會(huì)使用最新 name 值合成最新的字符串文本,如 Hello New World,然后使用渲染器更新 DOM 上對(duì)應(yīng)的文本。

注:更新元素節(jié)點(diǎn)和文本節(jié)點(diǎn)都提到了渲染器(renderer),這也是一個(gè)重要的概念。每一個(gè)視圖對(duì)象都有一個(gè) renderer 屬性,即是 Renderer2 的引用,也就是組件渲染器,DOM 的實(shí)際更新操作由它完成。因?yàn)?Angular 是跨平臺(tái)的,這個(gè) Renderer2 是個(gè)接口,這樣根據(jù)不同 Platform 就選擇不同的 Renderer。比如,在瀏覽器里這個(gè) Renderer 就是 DOMRenderer,在服務(wù)端就是 ServerRenderer,等等。從這里可看出,Angular 框架設(shè)計(jì)做了很好的抽象。
結(jié)論

我知道有大量難懂的信息需要消化,但是只要理解了這些知識(shí),你就可以更好的設(shè)計(jì)程序或者去調(diào)試 DOM 更新相關(guān)的問(wèn)題。我建議你按照本文提到的源碼邏輯,使用調(diào)試器或 debugger 語(yǔ)句 一步步去調(diào)試源碼。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/107723.html

相關(guān)文章

  • [] Angular 屬性綁定更新機(jī)制

    摘要:本文主要介紹輸入輸出綁定方式,特別是當(dāng)父組件輸入綁定值變化時(shí),如何更新子組件輸入值。更新指令的屬性上文中已經(jīng)描述了函數(shù)是用來(lái)更新元素的屬性,而是用來(lái)更新子組件的輸入綁定屬性,并且變更檢測(cè)期間傳入的參數(shù)就是函數(shù)。 原文鏈接:The mechanics of property bindings update in Angular showImg(https://segmentfault....

    tianhang 評(píng)論0 收藏0
  • [] $digest 在 Angular 中重生

    摘要:但如果一個(gè)組件在生命周期鉤子里改變父組件屬性,卻是可以的,因?yàn)檫@個(gè)鉤子函數(shù)是在更新父組件屬性變化之前調(diào)用的注即第步,在第步之前調(diào)用。 原文鏈接:Angular.js’ $digest is reborn in the newer version of Angular showImg(https://segmentfault.com/img/remote/146000001468785...

    incredible 評(píng)論0 收藏0
  • [] 為何 Angular 內(nèi)部沒(méi)有發(fā)現(xiàn)組件

    摘要:本質(zhì)上,本文主要解釋內(nèi)部是如何定義組件和指令的,并引入新的視圖節(jié)點(diǎn)定義指令定義。大多數(shù)指令使用屬性選擇器,但是有一些也選擇元素選擇器。實(shí)際上,表單指令就是使用元素選擇器來(lái)把特定行為附著在元素上。但是由于編譯器會(huì)為每一個(gè) 原文鏈接:Here is why you will not find components inside Angular showImg(https://segmen...

    LiveVideoStack 評(píng)論0 收藏0
  • [] 關(guān)于 `ExpressionChangedAfterItHasBeenCheckedErro

    摘要:本文將解釋引起這個(gè)錯(cuò)誤的內(nèi)在原因,檢測(cè)機(jī)制的內(nèi)部原理,提供導(dǎo)致這個(gè)錯(cuò)誤的共同行為,并給出修復(fù)這個(gè)錯(cuò)誤的解決方案。這一次過(guò)程稱(chēng)為。這個(gè)程序設(shè)計(jì)為子組件拋出一個(gè)事件,而父組件監(jiān)聽(tīng)這個(gè)事件,而這個(gè)事件會(huì)引起父組件屬性值發(fā)生改變。 原文鏈接:Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedE...

    andong777 評(píng)論0 收藏0
  • [] 你真的知道 Angular 單向數(shù)據(jù)流嗎

    摘要:所以,單向數(shù)據(jù)流的意思是指在變更檢測(cè)期間屬性綁定變更的架構(gòu)。相反,輸出綁定過(guò)程并沒(méi)有在變更檢測(cè)期間內(nèi)運(yùn)行,所以它沒(méi)有把單向數(shù)據(jù)流轉(zhuǎn)變?yōu)殡p向數(shù)據(jù)流。說(shuō)的單向數(shù)據(jù)流說(shuō)的是服務(wù)層,而不是視圖層嗷。 原文鏈接: Do you really know what unidirectional data flow means in?Angular 關(guān)于單向數(shù)據(jù)流,還可以參考這篇文章,且文中還有 y...

    fox_soyoung 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<