搜尋
首頁web前端js教程[譯] Angular DOM 更新機制 - Laravel/Angular 技術分享

[譯] Angular DOM 更新機制 - Laravel/Angular 技術分享

Sep 07, 2018 pm 04:24 PM
angular.jsjavascripttypescript

這篇文章主要的向大家介紹了angularjs dom的更新機制,還有關於angularjs的模型表達式和程式內部架構等很多知識點,下面就讓我們一起開始學習吧

angularjs的模型表達式:

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

<span>Hello {{name}}</span>

或類似下面的屬性綁定(註:這與上面代碼等價):

<span></span>

當每次name 值改變時,Angular 會神奇般的自動更新DOM 元素(註:最上面程式碼是更新DOM 文字節點,上面程式碼是更新DOM 元素節點,兩者是不一樣的,下文解釋)。這表面上看起來很簡單,但其內部工作卻相當複雜。而且,DOM 更新只是Angular 變更偵測機制 的一部分,變更偵測機制主要由以下三個步驟組成:

  • DOM updates(註:即本文將要解釋的內容)

  • child components Input bindings updates

  • ##query list updates

本文主要探討變更偵測機制的渲染部分(即DOM updates 部分)。如果你之前也對這個問題很好奇,可以繼續讀下去,絕對讓你茅塞頓開。

在引用相關原始碼時,假設程式是以生產模式運作。讓我們開始吧!

程式內部架構

在探索 DOM 更新之前,我們先搞清楚 Angular 程式內部究竟是如何設計的,簡單回顧一下吧。

視圖

從我的這篇文章

Here is what you need to know about dynamic components in Angular 知道Angular 編譯器會把程式中使用的元件編譯為一個工廠類別(factory)。例如,下面程式碼展示Angular 如何從工廠類中創建一個元件(註:這裡作者邏輯貌似有點亂,前一句說的Angular 編譯器編譯的工廠類,其實是編譯器去做的,不需要開發者做任何事情,是自動化的事情;而下面程式碼說的是開發者如何手動透過ComponentFactory 來建立一個Component 實例。總之,他是想說元件是怎麼被實例化的):

const factory = r.resolveComponentFactory(AComponent);
componentRef: ComponentRef<acomponent> = factory.create(injector);</acomponent>
Angular 使用這個工廠類別來實例化

View Definition ,然後使用viewDef 函數來建立視圖。 Angular 內部把一個程式看作為一個視圖樹,一個程式雖然有眾多元件,但有一個公共的視圖定義介面來定義由元件產生的視圖結構(註:即ViewDefinition Interface),當然Angular 使用每一個元件物件來建立對應的視圖,從而由多個視圖組成視圖樹。 (註:這裡有一個主要概念就是檢視,其架構為 ViewDefinition Interface

元件工廠

元件工廠大部分程式碼是由編譯器產生的不同視圖節點組成的,這些視圖節點是透過模板解析產生的(註:編譯器產生的元件工廠是一個傳回值為函數的函數,上文的ComponentFactory 是Angular 提供的類,供手動調用。當然,兩者指向同一個事物,只是表現形式不同而已)。假設定義一個元件的模板如下:

<span>I am {{name}}</span>
編譯器會解析這個模板產生包含如下類似的元件工廠程式碼(註:這只是最重要的部分程式碼):

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 元件編譯產生的工廠函數完整程式碼如下
 (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};})
上面程式碼描述了視圖的結構,並在實例化元件時會被呼叫。

jit_viewDef_1 其實就是viewDef 函數,用來建立視圖(註:viewDef 函數很重要,因為視圖是呼叫它所建立的,產生的視圖結構就是ViewDefinition)。

viewDef 函數的第二個參數 nodes 有些類似 html 中節點的意思,但卻不只如此。上面程式碼中第二個參數是一個數組,其第一個數組元素 jit_elementDef_2 是元素節點定義,第二個數組元素 jit_textDef_3 是文字節點定義。 Angular 編譯器會產生許多不同的節點定義,節點類型是由 NodeFlags 設定的。稍後我們將看到 Angular 如何根據不同節點類型來做 DOM 更新。

本文只對元素和文字節點感興趣:

export const enum NodeFlags {
    TypeElement = 1 讓我們簡單擼一遍。 <p></p><blockquote>注:上文作者说了一大段,其实核心就是,<strong>程序是一堆视图组成的,而每一个视图又是由不同类型节点组成的。而本文只关心元素节点和文本节点,至于还有个重要的指令节点在另一篇文章。</strong>
</blockquote><h3 id="元素节点的结构定义">元素节点的结构定义</h3><p><strong>元素节点结构</strong> 是 Angular 编译每一个 html 元素生成的节点结构,它也是用来生成组件的,如对这点感兴趣可查看 <strong>Here is why you will not find components inside Angular</strong>。元素节点也可以包含其他元素节点和文本节点作为子节点,子节点数量是由 <code>childCount</code> 设置的。</p><p>所有元素定义是由 <strong>elementRef</strong> 函数生成的,而工厂函数中的 <code>jit_elementDef_2()</code> 就是这个函数。<code>elementRef()</code> 主要有以下几个一般性参数:</p>
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

还有其他的几个具有特定性能的参数:

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

本文主要对 bindings 感兴趣。

注:从上文知道视图(view)是由不同类型节点(nodes)组成的,而元素节点(element nodes)是由 elementRef 函数生成的,元素节点的结构是由 ElementDef 定义的。

文本节点的结构定义

文本节点结构 是 Angular 编译每一个 html 文本 生成的节点结构。通常它是元素定义节点的子节点,就像我们本文的示例那样(注:<span>I am {{name}}</span>span 是元素节点,I am {{name}} 是文本节点,也是 span 的子节点)。这个文本节点是由 textDef 函数生成的。它的第二个参数以字符串数组形式传进来(注: Angular v5.* 是第三个参数)。例如,下面的文本:

<h1 id="Hello-name-and-another-prop">Hello {{name}} and another {{prop}}</h1>

将要被解析为一个数组:

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

然后被用来生成正确的绑定:

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

在脏检查(注:即变更检测)阶段会这么用来生成文本:

text
+ context[bindings[0][property]] + context[bindings[0][suffix]]
+ context[bindings[1][property]] + context[bindings[1][suffix]]
注:同上,文本节点是由 textDef 函数生成的,结构是由 TextDef 定义的。既然已经知道了两个节点的定义和生成,那节点上的属性绑定, Angular 是怎么处理的呢?

节点的绑定

Angular 使用 BindingDef 来定义每一个节点的绑定依赖,而这些绑定依赖通常是组件类的属性。在变更检测时 Angular 会根据这些绑定来决定如何更新节点和提供上下文信息。具体哪一种操作是由 BindingFlags 决定的,下面列表展示了具体的 DOM 操作类型:

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

元素和文本定义根据这些编译器可识别的绑定标志位,内部创建这些绑定依赖。每一种节点类型都有着不同的绑定生成逻辑(注:意思是 Angular 会根据 BindingFlags 来生成对应的 BindingDef)。(想看更多就到PHP中文网AngularJS开发手册中学习)

更新渲染器

最让我们感兴趣的是 jit_viewDef_1 中最后那个参数:

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

这个函数叫做 updateRenderer。它接收两个参数:_ck_v_ckcheck 的简写,其实就是 prodCheckAndUpdateNode 函数,而 _v 就是当前视图对象。updateRenderer 函数会在 每一次变更检测时 被调用,其参数 _ck_v 也是这时被传入。

updateRenderer 函数逻辑主要是,从组件对象的绑定属性获取当前值,并调用 _ck 函数,同时传入视图对象、视图节点索引和绑定属性当前值。重要一点是 Angular 会为每一个视图执行 DOM 更新操作,所以必须传入视图节点索引参数(注:这个很好理解,上文说了 Angular 会依次对每一个 view 做模型视图同步过程)。你可以清晰看到 _ck 参数列表:

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

nodeIndex 是视图节点的索引,如果你模板中有多个表达式:

<h1 id="Hello-name">Hello {{name}}</h1>
<h1 id="Hello-age">Hello {{age}}</h1>

编译器生成的 updateRenderer 函数如下:

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

现在我们已经知道 Angular 编译器生成的所有对象(注:已经有了 view,element node,text node 和 updateRenderer 这几个道具),现在我们可以探索如何使用这些对象来更新 DOM。

从上文我们知道变更检测期间 updateRenderer 函数传入的一个参数是 _ck 函数,而这个函数就是 prodCheckAndUpdateNode。这个函数在继续执行后,最终会调用 checkAndUpdateNodeInline ,如果绑定属性的数量超过 10,Angular 还提供了 checkAndUpdateNodeDynamic 这个函数(注:两个函数本质一样)。

checkAndUpdateNodeInline 函数会根据不同视图节点类型来执行对应的检查更新函数:

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

让我们看下这些函数是做什么的,至于 NodeFlags.TypeDirective 可以查看我写的文章 The mechanics of property bindings update in Angular

注:因为本文只关注 element node 和 text node

元素节点

对于元素节点,会调用函数 checkAndUpdateElementInline 以及 checkAndUpdateElementValuecheckAndUpdateElementValue 函数会检查绑定形式是否是 [attr.name, class.name, style.some] 或是属性绑定形式:

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

然后使用渲染器对应的方法来对该节点执行对应操作,比如使用 setElementClass 给当前节点 span 添加一个 class

文本节点

对于文本节点类型,会调用 checkAndUpdateTextInline ,下面是主要部分:

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

它会拿到 updateRenderer 函数传过来的当前值(注:即上文的 _ck(_v,4,0,currVal_1);),与上一次变更检测时的值相比较。视图数据包含有 oldValues 属性,如果属性值如 name 发生变化,Angular 会使用最新 name 值合成最新的字符串文本,如 Hello New World,然后使用渲染器更新 DOM 上对应的文本。(想看更多就到PHP中文网AngularJS开发手册中学习)

注:更新元素节点和文本节点都提到了渲染器(renderer),这也是一个重要的概念。每一个视图对象都有一个 renderer 属性,即是 Renderer2 的引用,也就是组件渲染器,DOM 的实际更新操作由它完成。因为 Angular 是跨平台的,这个 Renderer2 是个接口,这样根据不同 Platform 就选择不同的 Renderer。比如,在浏览器里这个 Renderer 就是 DOMRenderer,在服务端就是 ServerRenderer,等等。从这里可看出,Angular 框架设计做了很好的抽象。

结论

我知道有大量难懂的信息需要消化,但是只要理解了这些知识,你就可以更好的设计程序或者去调试 DOM 更新相关的问题。我建议你按照本文提到的源码逻辑,使用调试器或 debugger 语句 一步步去调试源码。

好了,這篇文章到這就結束了(想看更多就到PHP中文網AngularJS使用手冊中學習),有問題的可以在下方留言提問。

以上是[譯] Angular DOM 更新機制 - Laravel/Angular 技術分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
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廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

Python還是JavaScript更好?Python還是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。1.Python以简洁语法和丰富库生态著称,适用于数据分析和Web开发。2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安裝JavaScript?如何安裝JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安裝,因為它已內置於現代瀏覽器中。你只需文本編輯器和瀏覽器即可開始使用。 1)在瀏覽器環境中,通過標籤嵌入HTML文件中運行。 2)在Node.js環境中,下載並安裝Node.js後,通過命令行運行JavaScript文件。

在Quartz中如何在任務開始前發送通知?在Quartz中如何在任務開始前發送通知?Apr 04, 2025 pm 09:24 PM

如何在Quartz中提前發送任務通知在使用Quartz定時器進行任務調度時,任務的執行時間是由cron表達式設定的。現�...

在JavaScript中,如何在構造函數中獲取原型鏈上函數的參數?在JavaScript中,如何在構造函數中獲取原型鏈上函數的參數?Apr 04, 2025 pm 09:21 PM

在JavaScript中如何獲取原型鏈上函數的參數在JavaScript編程中,理解和操作原型鏈上的函數參數是常見且重要的任�...

微信小程序webview中Vue.js動態style位移失效是什麼原因?微信小程序webview中Vue.js動態style位移失效是什麼原因?Apr 04, 2025 pm 09:18 PM

在微信小程序web-view中使用Vue.js動態style位移失效的原因分析在使用Vue.js...

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中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中